Go SDK
Runtime contract enforcement for AI agent tool calls in Go. Full feature parity with the Python reference implementation.
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-goRequires 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 /tmpThe .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:
| Option | Type | Default | Description |
|---|---|---|---|
WithMode | string | "enforce" | "enforce" or "observe" -- panics on invalid |
WithEnvironment | string | "production" | Environment name for audit events |
WithContracts | ...any | none | Precondition, Postcondition, SessionContract values |
WithSandboxContracts | ...Precondition | none | Sandbox boundary contracts |
WithLimits | OperationLimits | defaults | Session and per-tool call limits |
WithAuditSink | ...Sink | collecting sink | External audit sinks |
WithRedaction | *Policy | default policy | Redaction policy for audit events |
WithBackend | StorageBackend | in-memory | Session storage backend |
WithPrincipal | *Principal | nil | Static principal for all calls |
WithPrincipalResolver | func | nil | Dynamic per-call principal resolution |
WithOnDeny | func | nil | Callback on denial |
WithOnAllow | func | nil | Callback on allow |
WithOnPostWarn | func | nil | Callback on postcondition warnings |
WithSuccessCheck | func | nil | Custom tool success check |
WithHooks | ...HookRegistration | none | Before/after hooks |
WithTools | map | none | Tool registry configuration |
WithApprovalBackend | Backend | nil | HITL approval backend |
WithPolicyVersion | string | "" | Policy version identifier |
Factory Constructors
| Constructor | Description |
|---|---|
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 utilityCore runs fully standalone. No server dependency. No adapter dependency. No framework dependency.
Next Steps
Go Adapters
Wire Edictum into ADK Go, LangChainGo, Eino, Anthropic SDK, or Genkit
Server SDK
Connect to Edictum Console for remote contract management and hot-reload
Observability
OpenTelemetry spans and metrics for governance decisions
Migration from Python
API comparison and Go idioms for Python developers
YAML Reference
Full contract YAML schema -- same format across Python, Go, and TypeScript
Contracts Deep Dive
Preconditions, postconditions, session limits, and sandboxes
Last updated on