# Runtime

{% tabs %}
{% tab title="End users" %}
Runtime settings control how CrossWatch behaves during runs.

Most users should leave defaults alone.

#### The only knobs most setups need

* `runtime.debug`: show extra detail in logs.
* `runtime.snapshot_ttl_sec`: keep this at `0` if you suspect staleness.
* `runtime.apply_chunk_size`: smaller chunks reduce blast radius.
* `runtime.apply_chunk_pause_ms`: add a pause if you hit rate limits.

#### Safe defaults

* Snapshot TTL: `0`
* Chunking: off, unless a provider is picky

Related:

* Caching behavior: [Caching layers](/blueprint-architecture/orchestrator/caching-layers.md)
* Chunk sizing: [Chunking](/blueprint-architecture/orchestrator/chunking.md)
  {% endtab %}

{% tab title="Power users" %}
This doc covers the “runtime” config that controls how the orchestrator behaves at run time: debug output, snapshot caching, suspect snapshot guards, apply chunking, telemetry warnings, and how the `ctx` object is built.

<details>

<summary>Implementation notes</summary>

**Primary code:** `orchestrator/facade.py` (`class Orchestrator`)\
**Also relevant:** `orchestrator/_chunking.py`, `orchestrator/_telemetry.py`, `orchestrator/_pairs.py`

</details>

***

### Where runtime config is read

The orchestrator reads runtime options from:

* `config["runtime"]` (primary)
* and telemetry thresholds from:
  * `config["telemetry"]`, or
  * `config["runtime"]["telemetry"]` (fallback)

All parsing happens in `Orchestrator.__post_init__()`.

***

### `runtime` options (actual keys in code)

#### `runtime.debug` (bool)

Default: `false`

* Enables `ctx.dbg(...)` output.
* Debug output is emitted as either:
  * a `debug` structured event (when fields are supplied), or
  * a plain `[DEBUG] ...` line.

#### `runtime.snapshot_ttl_sec` (int)

Default: `0` (disabled)

* If > 0, snapshots built via `_snapshots.build_snapshots_for_feature(...)` can be reused from `ctx.snap_cache` within the same run (until TTL expires).
* This only affects *within-run* caching; there is no cross-run snapshot cache.

#### `runtime.suspect_min_prev` (int)

Default: `20`

* Minimum baseline size required before “drop guard” will even consider a snapshot “suspect”.

#### `runtime.suspect_shrink_ratio` (float)

Default: `0.10`

Used in two places:

1. Drop guard (snapshot coercion): “did current snapshot shrink too much?”
2. Mass delete protection: “are planned removals too large?”

#### `runtime.suspect_debug` (bool)

Default: `true`

* When drop guard triggers, controls how noisy the debug reporting is.

#### `runtime.apply_chunk_size` (int)

Default: `0` (no chunking)

* When > 0, write batches are split into chunks of this size before calling provider `add/remove`.

#### `runtime.apply_chunk_pause_ms` (int)

Default: `0`

* Optional pause between chunks (milliseconds).

#### `runtime.apply_chunk_size_by_provider` (mapping)

Also accepted aliases (same behavior):

* `apply_chunk_sizes_by_provider`
* `apply_chunk_sizes`

Example:

```json
{
  "runtime": {
    "apply_chunk_size": 50,
    "apply_chunk_size_by_provider": {
      "SIMKL": 25,
      "TRAKT": 10
    }
  }
}
```

* Keys are uppercased internally.
* Values must be positive ints.
* When present, it overrides the base chunk size for that provider.
* Resolution logic lives in `orchestrator/_chunking.py` (`effective_chunk_size(ctx, provider_name)`).

***

### Telemetry warning thresholds

The orchestrator builds a threshold map at init:

```py
telem_cfg = cfg["telemetry"] or cfg["runtime"]["telemetry"] or {}
warn_thresholds = telem_cfg.get("warn_rate_remaining") or {
  "TRAKT": 100, "SIMKL": 50, "PLEX": 0, "JELLYFIN": 0
}
```

At the end of `run_pairs(...)`, it calls:

* `ctx.stats.record_summary(added=..., removed=...)`
* `ctx.emit_rate_warnings()`

`emit_rate_warnings()` delegates to `orchestrator/_telemetry.py`:

* `maybe_emit_rate_warnings(stats, emit, thresholds)`

This expects `stats.http_overview(hours=24)` to return per-provider “rate remaining” style info (depends on your `Stats` backend).

***

### The execution context (`ctx`)

`Orchestrator.context` returns a `SimpleNamespace` that is passed into `run_pairs(ctx)`.

Fields set today:

* `config`: full config dict (mutable copy)
* `providers`: loaded InventoryOps map
* `emit`: structured emitter (`Emitter.emit`)
* `emit_info`: raw line emitter (`Emitter.info`)
* `dbg`: debug emitter (gated by `runtime.debug`)
* `debug`: bool
* `dry_run`: bool
* `conflict`: conflict policy object (`ConflictPolicy`)
* `state_store`: `StateStore` instance
* `stats`: `Stats` instance (wrapper)
* `emit_rate_warnings`: bound method (calls telemetry warnings)
* `tomb_prune`: bound tombstone prune function
* `only_feature`: optional feature filter
* `write_state_json`: bool
* `state_path`: where to write state (default: `state_store.state`)
* `snap_cache`: dict used for within-run snapshot caching
* `snap_ttl_sec`: snapshot TTL seconds
* `apply_chunk_size`, `apply_chunk_pause_ms`, `apply_chunk_size_by_provider`

Pipelines treat `ctx` as a “bag of knobs” and avoid global variables.

***

### Run-time flags passed through `Orchestrator.run(...)`

`Orchestrator.run(...)` supports:

* `dry_run`: forces no writes (providers still called for reads)
* `only_feature`: run only a single feature (if supported by the pair)
* `write_state_json`: controls whether state changes are persisted
* `state_path`: custom state file location
* `progress`: controls output destination:
  * a callable: `progress(line)` receives every emitted string
  * `True`: print to stdout if no callback exists
  * `False`: silence output

Unknown `**kwargs` are ignored (debug event: `run.kwargs.ignored`).

***

### End-of-run extras

After `run_pairs(ctx)` returns, `Orchestrator.run()` also:

1. Persists a “wall”:
   * `_persist_state_wall(feature="watchlist")`
   * builds `state["wall"]` as a de-duplicated list of minimal baseline items across providers for that feature
2. Clears the UI hide file:
   * `state_store.clear_watchlist_hide()`
3. Emits metrics snapshots if available:
   * `http:overview` with `window_hours=24` (from `stats.http_overview(hours=24)`)
   * `stats:overview` (from `stats.overview(state)`)

***

### Related pages

* Snapshot TTL and drop guard: [Snapshots](/blueprint-architecture/orchestrator/snapshots.md)
* Chunking and retries: [Applier](/blueprint-architecture/orchestrator/applier.md), [Chunking](/blueprint-architecture/orchestrator/chunking.md)
* Suspect shrink ratio and delete guards: [Guardrails](/blueprint-architecture/orchestrator/guardrails.md)
* Event format and naming: [Eventing](/blueprint-architecture/orchestrator/eventing.md)
  {% endtab %}
  {% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wiki.crosswatch.app/blueprint-architecture/orchestrator/runtime.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
