Go SDK
The Go SDK provides Session lifecycle, Delegation, Gateway routing, and context propagation for Go services.
Use it for application authority, not management CRUD. Use Admin Package for control-plane automation.
Install
Section titled “Install”go get github.com/garudex-labs/caracal/packages/sdk/goConnect and Configure
Section titled “Connect and Configure”import caracal "github.com/garudex-labs/caracal/packages/sdk/go"
client, err := caracal.New()if err != nil { return err}New() loads exactly CARACAL_CONFIG when set; otherwise it loads CARACAL_* environment variables. It never searches home directories or default profile paths. Multiple credential modes fail at startup.
| Constructor | Use it when |
|---|---|
New() | Use normal deployment configuration. |
FromClientSecret(options) | Supply one complete static client-secret configuration directly. |
FromEnv(), FromConfig(path), FromCredentials(...) | Advanced explicit loading, profile, and dynamic credential paths. |
Make Your First Protected Call
Section titled “Make Your First Protected Call”The smallest complete integration pins a transport to one resource and sends a request through the Gateway:
package main
import ( "fmt" "io"
caracal "github.com/garudex-labs/caracal/packages/sdk/go")
func main() { client, err := caracal.New() if err != nil { panic(err) } defer client.Close()
governed, err := client.ApplicationTransport(nil, "resource://pipernet", caracal.ApplicationTransportOptions{ Scopes: []string{"pipernet:read"}, }) if err != nil { panic(err) } target, err := client.GatewayRequest("resource://pipernet", "/reports") if err != nil { panic(err) }
resp, err := governed.Get(target.URL) if err != nil { panic(err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { panic(fmt.Sprintf("protected call failed: %s", resp.Status)) } body, _ := io.ReadAll(resp.Body) fmt.Println(string(body))}The zone, application, and resource behind this call come from your runtime profile; Add SDK to Your App walks that setup end to end. The sections below group the client API by task.
Run Work in Sessions
Section titled “Run Work in Sessions”| API | Purpose |
|---|---|
client.Session(ctx, fn, opts...) | Run fn inside a governed Session with a bound context.Context; set SessionOptions.Authority to bound its authority and Task to record what the Session is for. Retry protection is automatic; ordinary code should leave IdempotencyKey empty. See Safe Retries and Idempotency for durable-source redelivery. |
client.StartSession(ctx, opts...) | Start a governed Session that outlives a block; auto-renews its generation-fenced lease and returns a *SessionHandle with Heartbeat, DeadlineAt, LeaseGeneration, and Close. Retry protection is automatic. |
client.AttachSession(ctx, sessionID, opts...) | Re-attach to an active persisted long-lived Session after a restart: atomically acquires a new generation, fences older holders, and returns the same handle StartSession does. |
Session patterns for long-lived services and restart recovery are walked through in Integrate the Go SDK.
Hand Off Authority Between Agents
Section titled “Hand Off Authority Between Agents”| API | Purpose |
|---|---|
client.Delegate(ctx, opts) | Delegate a slice of the current Session’s authority to a peer Session; returns the Delegation for the receiver to accept. A transient Coordinator failure is retried once under an idempotency key, so no duplicate Delegation is issued. |
client.RevokeDelegation(ctx, delegationID) | Revoke a Delegation issued by this application. |
client.AcceptDelegation(ctx, delegationID, opts...) | Present a received Delegation: derive a context carrying it. Pass AcceptDelegationOptions{Validate: true} to confirm with the Coordinator that the Delegation is live for the bound Session first. |
The full hand-off workflow, including safe constraint choices, lives in Implement Multi-Agent Delegation.
Propagate Context Across Services
Section titled “Propagate Context Across Services”| API | Purpose |
|---|---|
client.Headers(ctx, opts...) | Project the bound context to http.Header. |
client.BindFromRequest(ctx, req, opts...) | Bind inbound Caracal envelope headers. |
Call Protected Resources
Section titled “Call Protected Resources”| API | Purpose |
|---|---|
client.Transport(base, opts...) | Return an *http.Client that mints the use=gateway mandate from CallOptions.Scopes and applies Gateway routing. Set CallOptions.ApprovalID to consume an approved hold. |
client.ApplicationTransport(base, resourceID, opts) | Return an *http.Client pinned to one resource, calling as the application’s own identity; provisions its own Session pair and Delegation. Set ApplicationTransportOptions.ApprovalID to consume an approved hold. |
client.MintMandate(ctx, resourceID, scopes, opts...) | Mint a cached resource mandate carrying the bound Session and Delegation; returns an oauth.MintedMandate with Token and ExpiresInSeconds. Requires client-secret credentials. |
Use ApplicationTransport() when your service calls as itself; use Transport() inside a Session when work already runs under session or delegated authority. Propagation defaults to PropagationGatewayOnly; use PropagationAlways only for a known Caracal-aware direct service chain, and note that Gateway redirects are surfaced without automatically replaying the mandate. Application-transport provisioning starts one source/target Session pair and one narrowed Delegation per cache key: a cold call costs four provisioning calls plus the final SDK mint and Gateway STS exchange, a warm call still performs both per-request STS exchanges, and Close() retires backing Sessions best-effort. Wiring these transports into OpenAI, Anthropic, and other provider clients is covered in Provider Recipes.
Act for Federated Users and Approvals
Section titled “Act for Federated Users and Approvals”| API | Purpose |
|---|---|
client.FederateSubject(ctx, idToken, opts...) | Exchange a Federated user’s identity token for an Authority record and return a FederatedSubject with SubjectAuthorityRecordID, Token, and ExpiresInSeconds. Start attributed work with both SubjectAuthorityRecordID and SubjectAuthorityRecordToken: Token; Coordinator verifies the signed proof before binding the record. This does not by itself propagate the Federated user’s sub into later resource mandates. The returned mandate also remains the Federated user’s credential for supported approval and exchange paths. |
client.WaitForApproval(ctx, approvalID, timeout) | Long-poll an approval raised by an approval-gated mint; returns the final oauth.ApprovalState (ApprovalApproved, ApprovalRejected, ApprovalExpired, ApprovalConsumed, or ApprovalPending). |
sdk.WithApproval(ctx, client, timeout, fn) | Run an approval-gated operation end to end: on *oauth.ApprovalRequiredError the client waits for the decision and, once approved, invokes fn again with the approval id. |
Approval gating is a policy feature; Human Approval covers the tiers and the operator decision path.
Build Requests and Manage the Client
Section titled “Build Requests and Manage the Client”| API | Purpose |
|---|---|
client.GatewayRequest(resourceID, path) | Build explicit Gateway routing metadata. |
client.Identity(ctx) | The zone and application the client acts as, for logging and metric labels. |
client.Current(ctx) | Inspect the current Caracal context. |
client.Close() | Terminally close the client and application-transport Sessions best-effort. Repeated close is safe; later operations fail. |
Context Propagation
Section titled “Context Propagation”constraints := &caracal.DelegationConstraints{ MaxHops: 1, PolicyApproved: true,}Available fields are Resources, MaxDepth, MaxHops, TTLSeconds, PolicyApproved, ExpiresAt, and BroadReason.
PolicyApproved and BroadReason are audit/display metadata, not authorization decisions. Delegation constraints do not implement usage accounting; enforce consumable quotas in a durable domain transaction.
Protect Inbound Requests
Section titled “Protect Inbound Requests”Use BindFromRequest() to propagate a Caracal context after an upstream Gateway, adapter, or verify-engine verifier has accepted the inbound mandate. Set CallOptions.Verify to enforce the bearer token at the boundary itself. The callback receives the inbound token, returns an error on failure, and must return a non-nil complete *VerifiedClaims projection on success. ZoneID, ApplicationID, and Hop are authoritative. Empty optional authority fields are authoritatively absent and never fall back to unsigned caller baggage. In production propagation-only mode, set TrustedPropagation: true to state that an upstream boundary already enforced the request; omitting both modes fails closed. Use Verification Layer Overview when the Go service must enforce mandate signatures, scopes, targets, Session identity, Delegation, or revocation at its own boundary.
Trace context and non-Caracal baggage remain propagation data. Headers and Transport require a bound Caracal context by default. Pass caracal.CallOptions{AsApplication: true} only when the call should intentionally use the application subject token; inbound application binding discards caller-supplied Caracal authority baggage.
Errors and Observability
Section titled “Errors and Observability”STS denials return *oauth.CaracalError carrying Code, HTTPStatus, and RequestID, so callers branch with errors.As instead of matching message text. Approval-gated exchanges return *oauth.ApprovalRequiredError with the approval fields, and coordinator failures return *caracal.CoordinatorError with StatusCode, Method, and Path.
import oauth "github.com/garudex-labs/caracal/packages/oauth/go"
_, err := client.MintMandate(ctx, "resource://pipernet", []string{"pipernet:read"})var denied *oauth.CaracalErrorif errors.As(err, &denied) && denied.Code == "access_denied" { log.Printf("denied by policy (request %s)", denied.RequestID)}
client.OnEvent(func(event oauth.Event) { metrics.Timing("caracal."+event.Type, event.Duration, event.Ok)})OnEvent(hook) reports every control-plane operation: token.exchange (with Resources, Scopes, and Cached for cache hits), approval.wait (with ApprovalID and the final State), coordinator.call (with Method, Path, and Status), and delegation.accept (with DelegationID and SessionID, so delegation presentations are auditable client-side). Each event carries Ok and Duration, ready to bridge into any metrics or tracing system. A hook that panics is recovered and never disturbs the operation that emitted the event, and the call returns a disposer that removes the hook. Errors the platform reports carry Retryable(), a hint that transport-level congestion and availability failures are worth retrying while policy denials are not.
Bridging into Prometheus takes one hook; the same shape feeds an OpenTelemetry meter:
operations := prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "caracal_operations_total", Help: "Caracal control-plane operations",}, []string{"type", "ok"})latency := prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: "caracal_operation_duration_ms", Help: "Caracal control-plane operation latency",}, []string{"type"})
client.OnEvent(func(event oauth.Event) { operations.WithLabelValues(event.Type, strconv.FormatBool(event.Ok)).Inc() latency.WithLabelValues(event.Type).Observe(float64(event.Duration.Milliseconds()))})The Go module ships as a single flat package: the envelope codec, context plumbing, Coordinator functions (StartCoordinatorSession, AcquireSessionLease, CreateDelegation, HeartbeatSession, ListInboundDelegations, TerminateSession), and Session primitives (Session, StartSession, AttachSession, Delegate) sit beside the Caracal client rather than behind a separate advanced entrypoint.
Retries and Cleanup
Section titled “Retries and Cleanup”Session creation retries transient Coordinator failures twice under one generated idempotency key; Delegation creation retries once. STS exchange and Gateway requests are not automatically replayed. Always use the callback context passed to Session, close long-lived handles, and call Close() during shutdown. Close() is idempotent and terminal.
Transport Security and Credential Handling
Section titled “Transport Security and Credential Handling”Every control-plane client accepts a custom *http.Client - HTTPClient on CoordinatorClient and ClientSecretOptions, and the base argument of Transport - so deployments that require mutual TLS or a private CA toward the STS and coordinator inject a client with a tls.Config carrying certificates instead of patching defaults. Production configuration refuses plaintext http control-plane URLs outside loopback; CARACAL_ALLOW_INSECURE_CONFIG_URLS=true overrides that gate and logs a warning banner at startup so the exception stays visible until TLS is in place.
Tokens, client secrets, and minted mandates live in ordinary process memory for their lifetime: Go strings are immutable and garbage-collected, so the SDK cannot zeroize them, and anything able to read the process heap (a debugger, a core dump, a compromised dependency) can read them. Keep secrets out of logs and error trackers - the SDK never logs them and caps error bodies for the same reason - rely on short token lifetimes and the credentials resolver for rotation, and treat heap access as full credential compromise in your threat model.

