> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zespan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK config propagation — update live applications without redeployment

> Understand how ZespanPilot pushes SDK configuration changes to running applications in real time via the ingest response config version field.

SDK config propagation allows Zespan to push configuration changes — model overrides, fallback models, retry and timeout policies, and more — to your running application without a code change or redeployment. The mechanism is built into the ingest response and adds no latency to your LLM calls.

## How it works

Every time your SDK flushes a batch of events to `POST /v1/ingest`, the response body includes a `cv` (config version) integer:

```json theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
{
  "accepted": 12,
  "cv": 7
}
```

The SDK compares this `cv` value to the last version it fetched. If the server version is higher, the SDK makes a single background request to `GET /v1/sdk/config` and applies the returned configuration rules immediately — without blocking any LLM call in progress.

```
SDK flush → POST /v1/ingest → { accepted: N, cv: 7 }
                                         ↓ cv > local_cv?
                              GET /v1/sdk/config → new rules
                                         ↓
                              Apply rules to live client
```

The entire update cycle typically completes within one flush interval (default 2 seconds). From the perspective of your application code, nothing changes — all updates are applied transparently.

## What can be propagated

The following SDK behaviors can be updated at runtime via config propagation:

| Rule type        | Effect                                                                                     |
| ---------------- | ------------------------------------------------------------------------------------------ |
| `model_override` | Redirect calls for a given operation to a different model                                  |
| `ab_test`        | Route a percentage of calls to a candidate model for experimentation                       |
| `fallback`       | Switch to a fallback model when specific error conditions occur                            |
| `retry`          | Configure retry behavior (max retries, backoff, retryable status codes) for provider calls |
| `timeout`        | Set a request timeout for a specific operation                                             |
| `concurrency`    | Limit concurrent in-flight calls for a specific operation                                  |
| `cache`          | Configure per-operation caching settings (enabled, TTL, similarity threshold)              |
| `sampling`       | Adjust the fraction of events traced                                                       |
| `guardrail`      | Enable or disable a guardrail                                                              |
| `rate_limit`     | Set a request rate limit for a scope                                                       |
| `prompt_version` | Pin an operation to a specific prompt version                                              |

Config rules are scoped to a project. Changes you make via ZespanPilot apply to all instances of your application using that project's API key.

## Updating config from ZespanPilot

The only way to push a config change today is through [ZespanPilot](/dashboard/zespanpilot), the AI copilot accessible via **⌘J** in the dashboard. You can ask it in plain English:

* "Switch all GPT-4o calls to GPT-4o-mini"
* "Set the sample rate to 20% for the production project"
* "Add a fallback to GPT-4o-mini if the checkout-agent operation errors"
* "Enable guardrails on the support-agent project"

ZespanPilot translates your instruction into a config rule and increments the `cv` counter. Within the next flush cycle, all running SDK instances pick up the change.

<Warning>
  Config changes that affect model routing — switching models or enabling an A/B test — are considered high-risk operations. ZespanPilot requires confirmation before applying them. Only users with the **admin** or **owner** role can apply config changes — members see a permission error.
</Warning>

## Turning config propagation on or off

Config propagation is gated by two init options, and the default differs by language.

<Tabs>
  <Tab title="TypeScript">
    Config propagation is active whenever `projectId` is set — `enableZespanPilot` defaults to `true`. Set it to `false` to opt out even with `projectId` present:

    ```typescript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    import { zespan } from "@zespan/sdk";

    zespan.init({
      apiKey: process.env.ZESPAN_API_KEY!,
      projectId: process.env.ZESPAN_PROJECT_ID!,
      enableZespanPilot: false,
    });
    ```
  </Tab>

  <Tab title="Python">
    Config propagation is off by default. Pass both `project_id` and `enable_zespan_pilot=True` to turn it on:

    ```python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    import zespan

    zespan.init(
        api_key="zsp_your_api_key_here",
        project_id="proj_your_project_id",
        enable_zespan_pilot=True,
    )
    ```
  </Tab>
</Tabs>

When config propagation is off, the SDK ignores the `cv` field in ingest responses and never fetches remote config. All behavior is determined solely by the options passed to `init()`. See the [TypeScript SDK reference](/sdk/typescript#init-options) or [Python SDK reference](/sdk/python#init-options) for the full option list.

## Reading config programmatically

Everything above happens automatically — the SDK applies incoming rules to your LLM calls without any code on your part. `ConfigClient` also exposes a direct read and subscribe API, for cases where you want to inspect the config currently in effect or react to a change yourself (for example, logging which model is active for an operation, or emitting your own metric when a fallback kicks in).

### Getting the client instance

In TypeScript, the client exposes `configClient` as a public property — it's `null` if config propagation is disabled (see [Turning config propagation on or off](#turning-config-propagation-on-or-off) above).

In Python, the wired instance is held on the client's `_config_client` attribute. There's no public property wrapping it yet, so access it directly on the object returned by `get_client()`.

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  import { zespan } from "@zespan/sdk";

  const configClient = zespan.getClient().configClient;

  if (configClient) {
    const model = configClient.getModel("checkout-agent", "gpt-4o");
    const abTest = configClient.getAbTest("checkout-agent");

    configClient.on("model_override", (overrides) => {
      console.log("Model overrides updated:", overrides);
    });
  }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
  from zespan import get_client

  client = get_client()
  config_client = client._config_client

  if config_client:
      model = config_client.get_model("checkout-agent", "gpt-4o")
      ab_test = config_client.get_ab_test("checkout-agent")

      def on_model_override(overrides):
          print("Model overrides updated:", overrides)

      config_client.on("model_override", on_model_override)
      # config_client.off("model_override", on_model_override)  # deregister later
  ```
</CodeGroup>

### Accessor methods

| TypeScript                          | Python                                | Returns                                                                  | Description                                                                              |
| ----------------------------------- | ------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
| `getModel(operation, defaultModel)` | `get_model(operation, default_model)` | `string` / `str`                                                         | The override model for `operation`, or the passed-in default if none is set              |
| `getFallback(operation)`            | `get_fallback(operation)`             | `{ model, conditions } \| null`                                          | Fallback model and trigger conditions for `operation`, or `null`/`None`                  |
| `getRetryPolicy(operation)`         | `get_retry_policy(operation)`         | `{ enabled, maxRetries, backoffMultiplier, retryOnStatusCodes } \| null` | Retry policy for `operation`, or `null`/`None`                                           |
| `getTimeout(operation)`             | `get_timeout(operation)`              | `{ timeoutMs } \| null`                                                  | Timeout setting for `operation`, or `null`/`None`                                        |
| `getConcurrencyLimit(operation)`    | `get_concurrency_limit(operation)`    | `number \| null` / `int \| None`                                         | Concurrency limit for `operation`, or `null`/`None`                                      |
| `getAbTest(operation)`              | `get_ab_test(operation)`              | `{ candidateModel, splitPercentage } \| null`                            | A/B test config for `operation`, or `null`/`None`                                        |
| `getSamplingRate()`                 | `get_sampling_rate()`                 | `number` / `float`                                                       | The current trace sampling rate                                                          |
| `getCacheConfig(operation)`         | —                                     | `{ enabled, ttlSeconds, similarityThreshold } \| null`                   | Cache settings for `operation`. TypeScript only — no Python equivalent                   |
| `getRuntimeConfig()`                | —                                     | Full parsed config snapshot                                              | Every rule currently applied, keyed by operation. TypeScript only — no Python equivalent |

All accessors read from the config already applied locally — none of them make a network call.

### Subscribing to config changes

Both languages expose `on(event, callback)` to run code when a specific part of the config changes. Python also exposes `off(event, callback)` to deregister a listener; TypeScript's `ConfigClient` doesn't currently have an `off()` method.

The callback receives the full updated value for that field — for example, a `model_override` listener receives the complete map of operation → model, not just the entry that changed.

| Event             | Fired when                | TypeScript | Python |
| ----------------- | ------------------------- | ---------- | ------ |
| `model_override`  | A model override changes  | Yes        | Yes    |
| `sampling_config` | The sampling rate changes | Yes        | Yes    |
| `cache_config`    | Cache settings change     | Yes        | No     |
| `fallback_config` | Fallback settings change  | Yes        | Yes    |
| `retry_config`    | A retry policy changes    | Yes        | Yes    |
| `timeout_config`  | A timeout changes         | Yes        | Yes    |
| `ab_test_config`  | An A/B test changes       | Yes        | Yes    |

<Tip>
  Guardrail overrides, rate limits, and prompt version pins are applied silently in both languages — they don't currently emit a change event.
</Tip>

## Audit trail

Every config change applied via ZespanPilot is recorded in the audit log. Go to **Settings → Audit Log** to see who changed what, when, and from where. Each entry includes the rule type, the before and after values, and the user who made the change.

<Tip>
  Config propagation is most valuable for emergency interventions: switching a broken model to a fallback, dropping the sample rate during a cost spike, or disabling a guardrail that is blocking legitimate traffic. For planned changes, a code deployment is still preferable because it puts the change in version control.
</Tip>
