Skip to content

Flags Runtime

The runtime API is the day-to-day SDK experience. Flags are created via the Console UI or management API. The runtime declares typed flag handles in code, evaluates them locally using JSON Logic, and reacts to changes in real time via WebSocket.

Declare Flag Handles

Flag handles are local declarations that map a flag key to a typed default. They do NOT create flags on the server. The code-level default represents "what should this code path do if smplkit is unreachable?" — typically the safe/conservative value.

python
checkout_v2 = client.flags.booleanFlag("checkout-v2", default=False)
banner_color = client.flags.stringFlag("banner-color", default="red")
max_retries = client.flags.numberFlag("max-retries", default=3)

Context Provider

The context provider is a function called on every flag.get(). It returns a list of Context objects describing the current evaluation context (user, account, device, etc.). In a real app, this pulls from the current request, session, or DI container.

python
@client.flags.context_provider
def resolve_context():
    return [
        Context(
            "user",
            _current_user["id"],
            first_name=_current_user["first_name"],
            plan=_current_user["plan"],
            beta_tester=_current_user["beta_tester"],
        ),
        Context(
            "account",
            _current_account["id"],
            industry=_current_account["industry"],
            region=_current_account["region"],
            employee_count=_current_account["employee_count"],
        ),
    ]

Evaluate Flags

get() runs JSON Logic evaluation locally — no HTTP per call. The context provider is called, rules are matched in order, and the first matching rule's serve value is returned. If no rule matches, the environment default (or flag default, or code-level default) is used.

The FIRST call to get() triggers lazy initialization — it bulk-fetches all flag definitions and opens the WebSocket. Subsequent calls are pure local evaluation. There is no explicit connect() call.

Evaluate with Provider Context

python
checkout_result = checkout_v2.get()
banner_result = banner_color.get()
retries_result = max_retries.get()

TypeScript Note

The TypeScript SDK requires an explicit await client.flags.initialize() before the first get() call. In Python, lazy initialization happens automatically on first get().

Evaluate with Different Context

Switch the simulated context and re-evaluate. Different context produces different rule matches and different values.

python
set_simulated_context(
    user={"id": "user-002", "first_name": "Bob", "plan": "free", "beta_tester": False},
    account={"id": "small-biz", "industry": "retail", "region": "eu", "employee_count": 10},
)

checkout_result = checkout_v2.get()
banner_result = banner_color.get()
retries_result = max_retries.get()

Explicit Context Override

For edge cases (background jobs, tests, admin tools), pass context directly to get(). This bypasses the registered provider for that single call.

python
explicit_result = checkout_v2.get(context=[
    Context("user", "test-user", plan="free", beta_tester=False),
    Context("account", "test-account", region="jp"),
])

Change Listeners

Register callbacks that fire when flag definitions change via WebSocket. Two scoping levels: global (any flag) and flag-scoped (specific key).

Global Listener

python
@client.flags.on_change
def on_any_change(event):
    print(f"Flag '{event.key}' updated via {event.source}")

Flag-Scoped Listener

python
@client.flags.on_change("banner-color")
def on_banner_change(event):
    print("banner-color definition changed")

Go SDK

The Go SDK supports both flags.OnChangeKey("key", cb) and handle.OnChange(cb) for flag-scoped listeners.

Cache Statistics

The SDK caches resolved values. Repeated evaluations with identical context skip JSON Logic entirely. Use stats() to see cache hits and misses.

python
stats = client.flags.stats()
print(f"Cache hits: {stats.cache_hits}")
print(f"Cache misses: {stats.cache_misses}")

for _ in range(100):
    checkout_v2.get()

stats_after = client.flags.stats()
print(f"Cache hits after 100 reads: {stats_after.cache_hits}")

Context Registration

register() queues contexts for batch registration with the server. This populates the Console rule builder's autocomplete with real context types and attributes. flushContexts() forces an immediate flush.

python
client.flags.register([
    Context("user", _current_user["id"],
        first_name=_current_user["first_name"],
        plan=_current_user["plan"],
        beta_tester=_current_user["beta_tester"]),
    Context("account", _current_account["id"],
        industry=_current_account["industry"],
        region=_current_account["region"],
        employee_count=_current_account["employee_count"]),
])
await client.flags.flush_contexts()

Sync Client (Python)

For synchronous applications (Django, Flask, CLI tools), use SmplClient instead of AsyncSmplClient. The API is identical but without await.

python
from smplkit import SmplClient, Context

with SmplClient(environment="production", service="my-service") as client:
    @client.flags.context_provider
    def resolve_context():
        return [Context("user", request.user.id, plan=request.user.plan)]

    checkout = client.flags.booleanFlag("checkout-v2", default=False)

    if checkout.get():
        render_new_checkout()

    client.flags.register([
        Context("user", request.user.id, plan=request.user.plan),
    ])

Next Steps