Edictum
Go SDK

Go SDK

Runtime contract enforcement for AI agent tool calls in Go. Full feature parity with the Python reference implementation.

AI Assistance

Right page if: you want to install and use Edictum in a Go application, learn the Go SDK API, or see Go-specific code examples. Wrong page if: you want the Python SDK -- see https://docs.edictum.ai/docs/quickstart. For TypeScript, see https://docs.edictum.ai/docs/typescript. Gotcha: the Go SDK requires Go 1.25+. Core has zero runtime dependencies -- YAML support requires `gopkg.in/yaml.v3` (pulled automatically).

The Go SDK (edictum-go) is a full port of the Python reference implementation. It enforces contracts on AI agent tool calls with the same deterministic pipeline, YAML engine, and audit trail. 562 tests, all passing with -race.

Install

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

Requires Go 1.25+.

Quick Start with YAML

Save this as contracts.yaml:

apiVersion: edictum/v1
kind: ContractBundle
defaults:
  mode: enforce
contracts:
  - id: block-dotenv
    type: pre
    tool: read_file
    when:
      args.path: { contains: ".env" }
    then:
      effect: deny
      message: "Read of sensitive file denied: {args.path}"

  - id: block-destructive-commands
    type: pre
    tool: run_command
    when:
      any:
        - args.command: { starts_with: "rm " }
        - args.command: { starts_with: "DROP " }
    then:
      effect: deny
      message: "Destructive command denied: {args.command}"
package main

import (
	"context"
	"fmt"
	"log"

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

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

	ctx := context.Background()

	// Allowed: normal file read
	result, err := g.Run(ctx, "read_file", map[string]any{"path": "readme.txt"},
		func(args map[string]any) (any, error) {
			return fmt.Sprintf("contents of %s", args["path"]), nil
		})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("OK:", result)

	// DENIED: agent tries to read .env
	_, err = g.Run(ctx, "read_file", map[string]any{"path": ".env"},
		func(args map[string]any) (any, error) {
			return "should never execute", nil
		})
	if denied, ok := edictum.AsDenied(err); ok {
		fmt.Println("DENIED:", denied.Reason)
	}

	// DENIED: agent tries to rm -rf
	_, err = g.Run(ctx, "run_command", map[string]any{"command": "rm -rf /tmp"},
		func(args map[string]any) (any, error) {
			return "should never execute", nil
		})
	if denied, ok := edictum.AsDenied(err); ok {
		fmt.Println("DENIED:", denied.Reason)
	}
}

Expected output:

OK: contents of readme.txt
DENIED: Read of sensitive file denied: .env
DENIED: Destructive command denied: rm -rf /tmp

The .env file was never read. The rm -rf command never executed. Contracts are evaluated in Go, outside the LLM. The agent cannot talk its way past these checks.

Programmatic Contracts

Define contracts as Go structs when you need compile-time type checking or dynamic logic:

import (
	"context"
	"strings"

	"github.com/edictum-ai/edictum-go/contract"
	"github.com/edictum-ai/edictum-go/envelope"
	"github.com/edictum-ai/edictum-go/guard"
)

g := guard.New(
	guard.WithEnvironment("production"),
	guard.WithMode("enforce"),
	guard.WithContracts(
		contract.Precondition{
			Name: "deny-sensitive-paths",
			Tool: "*",
			Check: func(_ context.Context, env envelope.ToolEnvelope) (contract.Verdict, error) {
				if strings.Contains(env.FilePath(), "/.ssh/") {
					return contract.Fail("Access to .ssh is denied"), nil
				}
				return contract.Pass(), nil
			},
		},
	),
	guard.WithOnDeny(func(env envelope.ToolEnvelope, reason, name string) {
		log.Printf("DENIED: %s -- %s", env.ToolName(), reason)
	}),
)

Both YAML and programmatic contracts can be mixed in a single guard.

Constructor Options

The guard.New() function accepts functional options:

OptionTypeDefaultDescription
WithModestring"enforce""enforce" or "observe" -- panics on invalid
WithEnvironmentstring"production"Environment name for audit events
WithContracts...anynonePrecondition, Postcondition, SessionContract values
WithSandboxContracts...PreconditionnoneSandbox boundary contracts
WithLimitsOperationLimitsdefaultsSession and per-tool call limits
WithAuditSink...Sinkcollecting sinkExternal audit sinks
WithRedaction*Policydefault policyRedaction policy for audit events
WithBackendStorageBackendin-memorySession storage backend
WithPrincipal*PrincipalnilStatic principal for all calls
WithPrincipalResolverfuncnilDynamic per-call principal resolution
WithOnDenyfuncnilCallback on denial
WithOnAllowfuncnilCallback on allow
WithOnPostWarnfuncnilCallback on postcondition warnings
WithSuccessCheckfuncnilCustom tool success check
WithHooks...HookRegistrationnoneBefore/after hooks
WithToolsmapnoneTool registry configuration
WithApprovalBackendBackendnilHITL approval backend
WithPolicyVersionstring""Policy version identifier

Factory Constructors

ConstructorDescription
guard.FromYAML(path, opts...)Load from a YAML file or directory
guard.FromYAMLString(content, opts...)Load from a YAML string
guard.FromYAMLWithReport(path, opts...)Load from YAML with a composition report
guard.FromServer(url, apiKey, agentID, opts...)Connect to Edictum Console

Dry-Run Evaluation

Test contracts without executing the tool:

result := g.Evaluate(ctx, "read_file", map[string]any{"path": ".env"})
fmt.Println(result.Verdict)                  // "deny"
fmt.Println(result.DenyReasons[0])           // "Read of sensitive file denied: .env"
fmt.Println(result.Contracts[0].ContractID)  // "block-dotenv"

Evaluate is synchronous and never executes the tool. Session contracts are skipped (no session state). All matching contracts are evaluated exhaustively -- no short-circuit on first deny.

Observe Mode

Deploy contracts without denying anything:

g, err := guard.FromYAML("contracts.yaml", guard.WithMode("observe"))

In observe mode, calls that would be denied are logged as CALL_WOULD_DENY audit events but allowed to proceed. Review the audit trail, tune your contracts, then switch to "enforce".

Package Structure

github.com/edictum-ai/edictum-go/
├── guard/           # Top-level API -- New(), Run(), Evaluate(), FromYAML(), FromServer()
├── contract/        # Verdict, Precondition, Postcondition, SessionContract
├── envelope/        # ToolEnvelope, Principal, ToolRegistry, BashClassifier
├── pipeline/        # 5-stage governance pipeline
├── session/         # Session counters, MemoryBackend
├── audit/           # CollectingSink, CompositeSink, StdoutSink
├── redaction/       # RedactionPolicy (word-boundary matching, secret detection)
├── sandbox/         # Path, command, and domain sandboxing
├── yaml/            # YAML bundle loader, evaluator, compiler
├── server/          # Server SDK -- HTTP client, SSE, Ed25519 verification
├── approval/        # Approval backend interface
├── adapter/         # Framework adapters (5)
│   ├── adkgo/       # Google ADK Go
│   ├── langchaingo/ # LangChainGo
│   ├── eino/        # Eino/CloudWeGo
│   ├── anthropic/   # Anthropic SDK Go
│   └── genkit/      # Firebase Genkit
├── telemetry/       # OpenTelemetry integration
└── internal/        # shlex tokenizer, deepcopy utility

Core runs fully standalone. No server dependency. No adapter dependency. No framework dependency.

Next Steps

Last updated on

On this page