Go SDK
Runtime rule 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 rulesets on AI agent tool calls with the same deterministic pipeline, YAML engine, and decision log. 660+ tests, all passing with -race.
Install
go get github.com/edictum-ai/edictum-goRequires Go 1.25+.
Quick Start with YAML
Save this as rules.yaml:
apiVersion: edictum/v1
kind: Ruleset
defaults:
mode: enforce
rules:
- id: block-dotenv
type: pre
tool: read_file
when:
args.path: { contains: ".env" }
then:
action: block
message: "Read of sensitive file blocked: {args.path}"
- id: block-destructive-commands
type: pre
tool: run_command
when:
any:
- args.command: { starts_with: "rm " }
- args.command: { starts_with: "DROP " }
then:
action: block
message: "Destructive command blocked: {args.command}"package main
import (
"context"
"errors"
"fmt"
"log"
edictum "github.com/edictum-ai/edictum-go"
"github.com/edictum-ai/edictum-go/guard"
)
func main() {
g, err := guard.FromYAML("rules.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
})
var blocked *edictum.DeniedError
if errors.As(err, &blocked) {
fmt.Println("DENIED:", blocked.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 errors.As(err, &blocked) {
fmt.Println("DENIED:", blocked.Reason)
}
}Expected output:
OK: contents of readme.txt
DENIED: Read of sensitive file blocked: .env
DENIED: Destructive command blocked: rm -rf /tmpThe .env file was never read. The rm -rf command never executed. Rulesets are evaluated in Go, outside the LLM. The agent cannot talk its way past these checks.
Programmatic Rulesets
Define rulesets as Go structs when you need compile-time type checking or dynamic logic:
import (
"context"
"strings"
"github.com/edictum-ai/edictum-go/guard"
"github.com/edictum-ai/edictum-go/rule"
"github.com/edictum-ai/edictum-go/toolcall"
)
g := guard.New(
guard.WithEnvironment("production"),
guard.WithMode("enforce"),
guard.WithRules(
rule.Precondition{
Name: "block-sensitive-paths",
Tool: "*",
Check: func(_ context.Context, call toolcall.ToolCall) (rule.Decision, error) {
if strings.Contains(call.FilePath(), "/.ssh/") {
return rule.Fail("Access to .ssh is blocked"), nil
}
return rule.Pass(), nil
},
},
),
guard.WithOnBlock(func(call toolcall.ToolCall, reason, name string) {
log.Printf("BLOCKED: %s -- %s", call.ToolName(), reason)
}),
)Both YAML and programmatic rulesets 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 |
WithRules | ...any | none | Precondition, Postcondition, SessionRule values |
WithSandboxRules | ...Precondition | none | Sandbox boundary rulesets |
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 |
WithOnBlock | func | nil | Callback on block |
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 Control Plane |
Dry-Run Evaluation
Test rulesets without executing the tool:
result := g.Evaluate(ctx, "read_file", map[string]any{"path": ".env"})
fmt.Println(result.Decision) // "block"
fmt.Println(result.BlockReasons[0]) // "Read of sensitive file blocked: .env"
fmt.Println(result.Rules[0].RuleID) // "block-dotenv"Evaluate is synchronous and never executes the tool. Session rulesets are skipped (no session state). All matching rulesets are evaluated exhaustively -- no short-circuit on first block.
Observe Mode
Deploy rulesets without blocking anything:
g, err := guard.FromYAML("rules.yaml", guard.WithMode("observe"))In observe mode, calls that would be blocked are logged as CALL_WOULD_BLOCK audit events but allowed to proceed. Review the decision log, tune your rulesets, then switch to "enforce".
Package Structure
github.com/edictum-ai/edictum-go/
├── guard/ # Top-level API -- New(), Run(), Evaluate(), FromYAML(), FromServer()
├── rule/ # Decision, Precondition, Postcondition, SessionRule
├── toolcall/ # ToolCall, Principal, ToolRegistry, BashClassifier
├── pipeline/ # 5-stage enforcement 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 Control Plane for remote rule management and hot-reload
Observability
OpenTelemetry spans and metrics for enforcement decisions
Migration from Python
API comparison and Go idioms for Python developers
YAML Reference
Full rule YAML schema -- same format across Python, Go, and TypeScript
Rulesets Deep Dive
Preconditions, postconditions, session limits, and sandboxes
Last updated on