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
go get github.com/smplkit/go-sdk/v3The logging integration ships as separate modules so you only pull in the framework you use. Add the adapter for your logger:
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/zapRequires 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.
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.
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.
// 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.)
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.
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")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.
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.
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:
- Write —
checkout := client.Flags().BooleanFlag("checkout-v2", false)and branch oncheckout.Get(ctx). - Provision —
create_flag(key="checkout-v2", type="boolean", default=false). - Verify —
get_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.

