kyra docs

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-sdk

Getting 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:

FieldRequiredDefaultDescription
APIKeyYesYour Kyra API key (kyra_sk_...)
TimeoutNo5sPer-request timeout to Kyra
FailOpenNotrueIf true, allow tool execution when Kyra is unreachable; if false, treat as block
AgentIDNo""Agent identifier sent with every evaluate request; recommended for multi-agent
ModeNo"""enforce" or "shadow" — sent to server as ENFORCE / SHADOW
SessionIntentNo""Optional session-level intent hint
FrameworkNo"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 calls

Registration 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=1

Summary

StepWhat to do
1Implement kyra.Tool (Name, Description, Call) for each tool
2Create one *kyra.KyraGovernor with kyra.New(kyra.Config{...})
3Wrap tools with gov.Wrap(rawTools) and use the returned slice in your agent
4For each run: gc := kyra.FromHumanMessage(userMsg, agentID) and ctx := kyra.WithGovernanceContext(ctx, gc)
5Pass ctx into every governed tool.Call(ctx, params)
6Handle *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.