Go SDK
Reference for the Kyra Go SDK.
Kyra Go SDK
Runtime governance for AI agent actions in Go. Every tool call is evaluated by Kyra before execution; blocked actions return a clear error so your agent can respond safely.
Install
go get github.com/kyratech-io/go-sdkGetting started
package main
import (
"context"
"fmt"
kyra "github.com/kyratech-io/go-sdk"
)
// 1. Implement the Tool interface
type searchTool struct{}
func (t *searchTool) Name() string { return "search" }
func (t *searchTool) Description() string { return "Searches the database" }
func (t *searchTool) Call(ctx context.Context, params map[string]any) (any, error) {
query, _ := params["query"].(string)
return map[string]string{"result": "found: " + query}, nil
}
func main() {
// 2. Create governor (once per process or per agent)
gov := kyra.New(kyra.Config{
APIKey: "kyra_sk_...", // required
AgentID: "my-agent-v1",
FailOpen: false, // true = allow if Kyra unreachable
})
// 3. Wrap your tools
raw := []kyra.Tool{&searchTool{}}
tools := gov.Wrap(raw)
// 4. Create governance context from the user message and attach to context
gc := kyra.FromHumanMessage("Find orders for customer 123", "my-agent-v1")
ctx := kyra.WithGovernanceContext(context.Background(), gc)
// 5. Call the governed tool (Kyra evaluates first)
result, err := tools[0].Call(ctx, map[string]any{"query": "orders"})
if err != nil {
if e, ok := err.(*kyra.ErrGovernanceBlock); ok {
fmt.Println("Blocked by policy:", e.Msg)
return
}
fmt.Println("Tool error:", err)
return
}
fmt.Println("Result:", result)
}Configuration
Create the governor with kyra.New(kyra.Config{...}). Key fields:
| Field | Required | Default | Description |
|---|---|---|---|
APIKey | Yes | — | Your Kyra API key (kyra_sk_...) |
Timeout | No | 5s | Per-request timeout to Kyra |
FailOpen | No | true | If true, allow tool execution when Kyra is unreachable; if false, treat as block |
AgentID | No | "" | Agent identifier sent with every evaluate request; recommended for multi-agent |
Mode | No | "" | "enforce" or "shadow" — sent to server as ENFORCE / SHADOW |
SessionIntent | No | "" | Optional session-level intent hint |
Framework | No | "GO" | Reported in wire format |
Example (fail-closed, enforce mode):
gov := kyra.New(kyra.Config{
APIKey: os.Getenv("KYRA_API_KEY"),
AgentID: "refund-agent-v1",
FailOpen: false,
Mode: "enforce",
Timeout: 15 * time.Second,
})Tool interface
Any struct that implements these three methods is a Kyra tool:
type Tool interface {
Name() string
Description() string
Call(ctx context.Context, params map[string]any) (any, error)
}Example:
type issueRefundTool struct {
paymentClient *PaymentClient
}
func (t *issueRefundTool) Name() string {
return "issue_refund"
}
func (t *issueRefundTool) Description() string {
return "Issues a refund for the given order and amount"
}
func (t *issueRefundTool) Call(ctx context.Context, params map[string]any) (any, error) {
orderID, _ := params["order_id"].(string)
amount, _ := params["amount"].(float64)
// ... call payment gateway, return result or error
return map[string]any{"refund_id": "rf_123", "status": "completed"}, nil
}Optional: tier hint
If your tool is always at least a certain risk tier, implement TierHinter. The server will use max(llmClassifiedTier, requestedTier).
func (t *issueRefundTool) RequestedTier() string { return "T2" }Use the wrapped tools (from gov.Wrap(rawTools)) in your agent loop. Each Call goes to Kyra first; only on ALLOW is your implementation run.
Governance context (per run)
Every run should have one root governance context created from the user’s message. Attach it to context.Context and pass that context into all governed tool calls.
Create and attach
// At the start of the run, when the user message is known
gc := kyra.FromHumanMessage(userMessage, "my-agent-v1")
ctx := kyra.WithGovernanceContext(context.Background(), gc)
// Pass ctx to every tool call
result, err := governedTool.Call(ctx, params)Retrieve from context
gc := kyra.GovernanceContextFrom(ctx)
if gc != nil {
fmt.Println("TraceID:", gc.TraceID, "SessionID:", gc.SessionID)
}GovernanceContext carries trace ID, session ID, original intent, action count, and highest tier — so Kyra can enforce policy across the whole run.
Trace ID: Each evaluate request sends a trace ID (from the governance context when present). You can set it when creating the context, or leave it unset so Kyra auto-generates one. The decision response always includes the trace ID used for that evaluation.
Multi-agent: child context
When an orchestrator delegates to a sub-agent, create a child governance context so Kyra can link the sub-agent’s trace to the parent.
// In the orchestrator's delegate tool
parentGC := kyra.GovernanceContextFrom(ctx)
childGC := kyra.NewChildContext(parentGC, "refund-agent-v1")
childCtx := kyra.WithGovernanceContext(ctx, childGC)
// Run the sub-agent with childCtx; its tool calls will send parentTraceId / parentAgentId
result, err := refundAgent.Run(childCtx, params)NewChildContext sets a new trace ID, keeps session and intent from the parent, and sets ParentTraceID and ParentAgentID for the server.
Agent registration (optional)
Registering your agent with Kyra is optional. If you do it, the returned agent ID is used in subsequent evaluate requests and can be used for topology (delegation) enforcement.
ctx := context.Background()
tools := []kyra.Tool{&searchTool{}, &issueRefundTool{}}
agentID, err := gov.RegisterAgent(ctx, "Refund Agent", systemPrompt, tools)
if err != nil {
log.Warn("registration failed (non-fatal)", "error", err)
}
// agentID is stored on the governor and used for future Evaluate callsRegistration with topology (delegation rules)
For multi-agent setups you can declare which agents this agent is allowed to delegate to:
topology := &kyra.TopologyDto{
DelegationWhitelist: map[string][]string{
"orchestrator-v1": {"refund-agent-v1", "search-agent-v1"},
},
}
agentID, err := gov.RegisterAgent(ctx, "Orchestrator", systemPrompt, tools, topology)- DelegationWhitelist: map of agent ID → list of agent IDs this agent may delegate to. If set, any other callee is blocked by Kyra.
- DelegationBlacklist: map of agent ID → list of agent IDs this agent must not delegate to (checked before whitelist).
Error handling
Governed tools return *kyra.ErrGovernanceBlock when Kyra blocks, escalates, or when the server is unreachable and FailOpen is false.
result, err := tool.Call(ctx, params)
if err != nil {
var blocked *kyra.ErrGovernanceBlock
if errors.As(err, &blocked) {
log.Warn("Kyra blocked action", "reason", blocked.Msg)
// Inform user or trigger escalation flow
return
}
// Other tool error
return err
}ErrGovernanceBlock.Msg is the block reason and is safe to log or show to users.
Propagating trace across HTTP (KyraTransport)
If your tools call downstream HTTP services, wrap the client’s transport so outbound requests carry Kyra trace headers:
client := &http.Client{
Transport: &kyra.KyraTransport{Base: http.DefaultTransport},
}
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.example.com/orders", nil)
resp, err := client.Do(req)Requests made with a ctx that contains a governance context will include X-Kyra-Trace and related headers so downstream systems can correlate with the same run.
Build and test
From the SDK root:
go build ./...
go test ./... -v -count=1Summary
| Step | What to do |
|---|---|
| 1 | Implement kyra.Tool (Name, Description, Call) for each tool |
| 2 | Create one *kyra.KyraGovernor with kyra.New(kyra.Config{...}) |
| 3 | Wrap tools with gov.Wrap(rawTools) and use the returned slice in your agent |
| 4 | For each run: gc := kyra.FromHumanMessage(userMsg, agentID) and ctx := kyra.WithGovernanceContext(ctx, gc) |
| 5 | Pass ctx into every governed tool.Call(ctx, params) |
| 6 | Handle *kyra.ErrGovernanceBlock when a call is blocked |
Optional: call RegisterAgent at startup (with optional TopologyDto for multi-agent), and use NewChildContext when delegating from an orchestrator to a sub-agent.