Skip to content

Go SDK

This is the in-code half of smplkit: one client resolves flags, reads config, controls log levels, and emits audit events. Manage the underlying resources — creating flags, setting config values, changing log levels — with the MCP tools or the Console.

Install

bash
go get github.com/smplkit/go-sdk/v3

The logging integration ships as separate modules so you only pull in the framework you use. Add the adapter for your logger:

bash
go get github.com/smplkit/go-sdk/logging/adapters/slog
# or, for go.uber.org/zap:
go get github.com/smplkit/go-sdk/logging/adapters/zap

Requires Go 1.24+. These commands fetch the latest published version.

Initialize the client

A single client exposes all four products as sub-clients: client.Flags(), client.Config(), client.Logging(), and client.Audit(). The SDK key comes from the SMPLKIT_API_KEY env var or the ~/.smplkit profile (or an explicit APIKey field). Set Environment once on the client — it's never a per-call argument; flag, config, and logger resolution all happen per-environment.

go
package main

import (
	"context"
	"log"

	smplkit "github.com/smplkit/go-sdk/v3"
)

func main() {
	ctx := context.Background()

	// APIKey, Environment, Service may also come from SMPLKIT_* env vars
	// or ~/.smplkit; explicit Config fields take precedence.
	client, err := smplkit.NewClient(smplkit.Config{
		Environment: "production",
		Service:     "my-service",
	})
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	// Optional: block until the live-updates WebSocket is registered, so
	// on-change listeners catch writes that happen right after startup.
	if err := client.WaitUntilReady(ctx, 0); err != nil {
		log.Fatal(err)
	}
}

The runtime initializes lazily — there's no explicit connect() step. WaitUntilReady(ctx, 0) is only needed for deterministic boot (showcases, tests, listeners firing immediately after construction). Runtime evaluation is synchronous; only the management/CRUD and audit-recording paths do network I/O.

Flags

Resolve a flag whenever code needs to branch on a feature rollout or targeting rule. Declare a typed handle with a safe default, then call .Get(ctx) — local JSON-Logic evaluation, no error returned. The default is served if the flag is missing or smplkit is unreachable, so pick the conservative value.

go
checkout := client.Flags().BooleanFlag("checkout-v2", false)

// Ambient context: a provider runs on every Get that doesn't pass an
// explicit context. Wire it once (e.g. in HTTP middleware).
client.Flags().SetContextProvider(func(ctx context.Context) []smplkit.Context {
	return []smplkit.Context{
		smplkit.NewContext("user", "alice@acme.com", nil,
			smplkit.WithAttr("plan", "enterprise"),
			smplkit.WithAttr("beta_tester", true),
		),
	}
})

if checkout.Get(ctx) {
	renderNewCheckout()
}

// Per-call context overrides the provider for a single evaluation.
basic := smplkit.NewContext("user", "u-1", nil, smplkit.WithAttr("plan", "free"))
_ = checkout.Get(ctx, basic)

Go attaches context via the SetContextProvider callback (ambient) or variadic Get(ctx, contexts...) arguments (per-call override).

→ Full reference (every language, all context modes, change listeners): Flags Runtime

Config

Read config when a value should be tunable without a redeploy. GetValueOr fetches a single key with a default (the form with a default never errors); Bind populates a struct or map and keeps it live.

go
// Single value — default returned if the key is absent.
slowMs := client.Config().GetValueOr(ctx, "database", "slow_query_threshold_ms", 500)

// Bind a typed struct: the SDK fills it and mutates it in place as the
// platform pushes changes over the WebSocket.
type Billing struct {
	Plan struct {
		MaxSeats int `json:"max_seats"`
	} `json:"plan"`
}
billing := &Billing{}
if err := client.Config().Bind(ctx, "billing", billing); err != nil {
	log.Fatal(err)
}
// billing.Plan.MaxSeats now reflects the latest config, and updates
// automatically when an admin changes it.

Bind returns a live object mutated in place — read fields directly; there's no per-read call. (Plain GetValue returns an error on a missing key; prefer GetValueOr when you have a sensible default.)

Config Runtime

Logging

Wire logging once at startup to hand log-level control to smplkit; after that, use your logger normally and the platform decides what gets emitted per service+environment. Go supports the standard library log/slog and go.uber.org/zap via their adapter modules. Register the adapter before calling Install.

go
import (
	"log/slog"

	slogadapter "github.com/smplkit/go-sdk/logging/adapters/slog"
	smplkit "github.com/smplkit/go-sdk/v3"
)

adapter := slogadapter.New()
adapter.InstallDefault() // route slog.Default() through the wrapper
client.Logging().RegisterAdapter(adapter)

if err := client.Logging().Install(ctx); err != nil {
	log.Fatal(err)
}

// Use slog as usual — levels are now server-driven.
slog.Info("checkout completed")
slog.Debug("filtered unless the platform level is DEBUG")

Logging Runtime

Audit

Record an audit event whenever something happens that you'd want on an immutable trail — invoices, permission changes, deletions. Record is fire-and-forget (buffered, retried, de-duplicated); call Flush for durability before a process exits or in tests.

go
import "time"

err := client.Audit().Events().Record(smplkit.CreateEventInput{
	EventType:    "invoice.created",
	ResourceType: "invoice",
	ResourceID:   "o-1",
	ActorLabel:   "finance@example.com",
	Category:     "billing",
	Severity:     "WARN",
	Data: map[string]interface{}{
		"snapshot": map[string]interface{}{"total_cents": 4900, "currency": "USD"},
	},
})
if err != nil {
	log.Fatal(err)
}

// Block until buffered events are delivered (CLI / tests / before exit).
client.Audit().Events().Flush(2 * time.Second)

EventType, ResourceType, and ResourceID are required. ResourceType must not start with smpl. — that prefix is reserved and the server rejects it.

Emit audit events

The author + operate loop

Each runtime call pairs with a resource you provision and verify out of band. Write the SDK call, create the resource with the MCP tools (create_flag / create_config / set_log_level), then confirm it with the read tools (get_flag / get_config / query_events). The handle id in code must equal the key you provision.

For a checkout kill-switch in Go:

  1. Writecheckout := client.Flags().BooleanFlag("checkout-v2", false) and branch on checkout.Get(ctx).
  2. Provisioncreate_flag(key="checkout-v2", type="boolean", default=false).
  3. Verifyget_flag(key="checkout-v2").

Flip the flag's rules later with the MCP tools or Console and checkout.Get(ctx) reflects the change in real time — no redeploy.