Edictum
Go SDK

Go Adapters

Five framework adapters for enforcing contracts in Google ADK Go, LangChainGo, Eino, Anthropic SDK Go, and Firebase Genkit.

AI Assistance

Right page if: you want to wire Edictum into a Go AI framework -- ADK Go, LangChainGo, Eino, Anthropic SDK, or Genkit. Wrong page if: you want Python adapters -- see https://docs.edictum.ai/docs/adapters/overview. For the Go SDK overview, see https://docs.edictum.ai/docs/go. Gotcha: all Go adapters use the same WrapTool() pattern. The only difference is the function signature they accept -- map[string]any, string, or json.RawMessage.

Edictum ships five Go framework adapters. Each is a thin wrapper around guard.Run() that translates framework-specific function signatures into the governance pipeline. All allow/deny logic lives in the pipeline, not in the adapters.

Switching frameworks requires changing only the adapter wiring, not your contracts. The same YAML works across all five adapters and the Python SDK.

Quick Comparison

AdapterFrameworkImport PathTool Signature
adkgoGoogle ADK Goadapter/adkgofunc(ctx, map[string]any) (any, error)
langchaingoLangChainGoadapter/langchaingofunc(ctx, string) (string, error)
einoEino/CloudWeGoadapter/einofunc(ctx, map[string]any) (any, error)
anthropicAnthropic SDK Goadapter/anthropicfunc(ctx, json.RawMessage) (any, error)
genkitFirebase Genkitadapter/genkitfunc(ctx, map[string]any) (any, error)

All adapters have zero external framework dependencies. They only import github.com/edictum-ai/edictum-go/guard.

Common Pattern

Every adapter follows the same three steps:

// 1. Create the guard
g, err := guard.FromYAML("contracts.yaml")

// 2. Create the adapter
adapter := adkgo.New(g)

// 3. Wrap each tool
wrappedTool := adapter.WrapTool("tool_name", originalToolFunc)

The wrapped function has the same signature as the original. Drop it into your agent where the original function was used.


Google ADK Go

Install

go get github.com/edictum-ai/edictum-go

Create adapter

import (
	"github.com/edictum-ai/edictum-go/adapter/adkgo"
	"github.com/edictum-ai/edictum-go/guard"
)

g, err := guard.FromYAML("contracts.yaml")
if err != nil {
	log.Fatal(err)
}

adapter := adkgo.New(g)

Wire into agent

// Original tool function
func readFile(ctx context.Context, args map[string]any) (any, error) {
	path := args["path"].(string)
	data, err := os.ReadFile(path)
	return string(data), err
}

// Wrap with governance -- same signature, enforced contracts
governedReadFile := adapter.WrapTool("read_file", readFile)

// Use governedReadFile in your ADK agent configuration

On deny, WrapTool returns an *edictum.DeniedError. On postcondition redaction, the result is modified before returning.


LangChainGo

LangChainGo tools take a string input and return a string result. The adapter parses JSON input into map[string]any for the pipeline, falling back to {"input": rawString} for non-JSON input.

Install

go get github.com/edictum-ai/edictum-go

Create adapter

import (
	"github.com/edictum-ai/edictum-go/adapter/langchaingo"
	"github.com/edictum-ai/edictum-go/guard"
)

g, err := guard.FromYAML("contracts.yaml")
if err != nil {
	log.Fatal(err)
}

adapter := langchaingo.New(g)

Wire into agent

// Original LangChainGo tool function
func searchDB(ctx context.Context, input string) (string, error) {
	// input is JSON: {"query": "SELECT * FROM users"}
	return db.Execute(input)
}

// Wrap with governance
governedSearch := adapter.WrapTool("search_db", searchDB)

// Use governedSearch wherever you registered the original tool

The adapter parses input as JSON for contract evaluation. If the input is not valid JSON, it wraps it as {"input": "raw string"} so contracts can still match against args.input.


Eino (CloudWeGo)

Eino tools use map[string]any for arguments and return any -- the same signature as guard.Run(). This makes it the simplest adapter.

Install

go get github.com/edictum-ai/edictum-go

Create adapter

import (
	"github.com/edictum-ai/edictum-go/adapter/eino"
	"github.com/edictum-ai/edictum-go/guard"
)

g, err := guard.FromYAML("contracts.yaml")
if err != nil {
	log.Fatal(err)
}

adapter := eino.New(g)

Wire into agent

// Original Eino tool function
func executeCmd(ctx context.Context, args map[string]any) (any, error) {
	cmd := args["command"].(string)
	return exec.CommandContext(ctx, "sh", "-c", cmd).Output()
}

// Wrap with governance
governedCmd := adapter.WrapTool("execute_cmd", executeCmd)

Anthropic SDK Go

The Anthropic SDK passes tool input as json.RawMessage. The adapter unmarshals it to map[string]any for the governance pipeline, preserving the original RawMessage for the underlying tool function.

Install

go get github.com/edictum-ai/edictum-go

Create adapter

import (
	"encoding/json"

	"github.com/edictum-ai/edictum-go/adapter/anthropic"
	"github.com/edictum-ai/edictum-go/guard"
)

g, err := guard.FromYAML("contracts.yaml")
if err != nil {
	log.Fatal(err)
}

adapter := anthropic.New(g)

Wire into agent

// Original Anthropic SDK tool function
func readFile(ctx context.Context, input json.RawMessage) (any, error) {
	var args struct {
		Path string `json:"path"`
	}
	if err := json.Unmarshal(input, &args); err != nil {
		return nil, err
	}
	data, err := os.ReadFile(args.Path)
	return string(data), err
}

// Wrap with governance
governedReadFile := adapter.WrapTool("read_file", readFile)

If the input is null or empty, the adapter passes an empty map[string]any{} to the pipeline.


Firebase Genkit

Genkit tools use map[string]any for arguments and return any, identical to Eino.

Install

go get github.com/edictum-ai/edictum-go

Create adapter

import (
	"github.com/edictum-ai/edictum-go/adapter/genkit"
	"github.com/edictum-ai/edictum-go/guard"
)

g, err := guard.FromYAML("contracts.yaml")
if err != nil {
	log.Fatal(err)
}

adapter := genkit.New(g)

Wire into agent

// Original Genkit tool function
func writeFile(ctx context.Context, args map[string]any) (any, error) {
	path := args["path"].(string)
	content := args["content"].(string)
	return nil, os.WriteFile(path, []byte(content), 0644)
}

// Wrap with governance
governedWrite := adapter.WrapTool("write_file", writeFile)

Using guard.Run() Directly

If your framework is not listed, or you do not use a framework, call guard.Run() directly:

result, err := g.Run(ctx, "read_file", map[string]any{"path": "/etc/passwd"},
	func(args map[string]any) (any, error) {
		return os.ReadFile(args["path"].(string))
	},
)

You get the same pipeline -- preconditions, sandbox, execution, postconditions, audit -- without any adapter. All five adapters above are thin wrappers around this method.

Per-Call Options

Override guard-level settings for a single Run() call:

result, err := g.Run(ctx, "read_file", args, toolFn,
	guard.WithSessionID("custom-session"),
	guard.WithRunEnvironment("staging"),
	guard.WithRunPrincipal(&envelope.Principal{
		UserID: "alice",
		Role:   "admin",
	}),
)

Next Steps

Last updated on

On this page