Runtime archive, deduplication, and async scheduling

Business context: Production explain runs generate thousands of coalition evaluations. Persisted archives support compliance replay, deduplication avoids redundant API spend, and bounded concurrency keeps latency predictable under load.

Goal: Walk through every public runtime API in nlp-shap 0.1.4 — run archive, coalition keys, hot LRU cache, and the async inference scheduler.

Prerequisites: nlp-shap 0.1.4+, Python 3.12.

Theory: See the runtime theory page.

Expected output: 100 coalition rows round-trip through SQLite; ten unique masks collapse to ten backend calls; scheduler metrics report cache hits and bounded concurrency.

import asyncio
import json
from pathlib import Path
import tempfile

from nlp_shap import Estimand
from nlp_shap.domain.conversation import ConversationSnapshot, Message, Role, Turn
from nlp_shap.domain.estimands import estimand_to_wire
from nlp_shap.masking.codec import MaskCodec
from nlp_shap.pipeline.config import DedupConfig, GenerationConfig
from nlp_shap.pipeline.manifest import RunManifest, parse_manifest
from nlp_shap.runtime import (
    CoalitionDedupRegistry,
    CoalitionJob,
    CoalitionRecordDraft,
    HotResultStore,
    InferenceScheduler,
    RunArchive,
    build_coalition_key,
    dedup_enabled,
)

Persist coalition history

Business: Audit teams need immutable run folders that pair attribution metadata with the exact generations that produced each coalition utility.

Technical: RunArchive.open writes manifest.json, SQLite rows, and blob files. history_lazy streams records without bulk-loading blobs.

turn = Turn(messages=(Message(role=Role.USER, text="Who are you?"),))
snapshot = ConversationSnapshot.from_turns((turn,))
manifest = RunManifest(estimand=Estimand.SHAPLEY, run_id="runtime-demo")
packed = MaskCodec.pack((True, False, True))

with tempfile.TemporaryDirectory() as tmp:
    root = Path(tmp) / "runtime-demo"
    with RunArchive.open(root, manifest, flush_every=25) as archive:
        for index in range(100):
            archive.append(
                CoalitionRecordDraft(
                    snapshot_id=snapshot.snapshot_id,
                    coalition_key=f"key-{index % 10}",
                    mask=packed,
                    absence_policy="delete",
                    model_id="mock",
                    generation_text=f"generation-{index}",
                    utility=float(index),
                    elapsed_ms=float(index),
                    cache_hit=index % 2 == 0,
                )
            )

    payload = parse_manifest(json.loads((root / "manifest.json").read_text()))
    with RunArchive.open(root, manifest) as archive:
        records = list(archive.history_lazy())

print(payload.estimand, estimand_to_wire(payload.estimand), len(records))
shapley shapley 100

Coalition dedup keys

Business: Identical coalition prompts at temperature zero should not trigger duplicate model calls — a direct infrastructure cost control.

Technical: build_coalition_key hashes snapshot, players, packed mask, policy, model, and generation settings. dedup_enabled follows config auto/on/off.

generation = GenerationConfig(temperature=0.0)
key = build_coalition_key(
    snapshot_id=snapshot.snapshot_id,
    player_ids=("p0", "p1"),
    mask_present=(True, False),
    absence_policy="delete",
    model_id="mock",
    generation=generation,
)
print("dedup auto:", dedup_enabled(DedupConfig(enabled="auto"), generation))
print("key prefix:", key[:16])

registry = CoalitionDedupRegistry()
print("new:", registry.observe(key))
print("repeat:", registry.observe(key))
print("unique:", len(registry))
dedup auto: True
key prefix: 903952d03ee4df35
new: True
repeat: False
unique: 1

Hot LRU cache

Business: Recent coalitions often repeat within one explain run; an in-memory LRU avoids even hitting the dedup registry on hot paths.

Technical: HotResultStore keeps the most recent coalition generations up to maxsize.

store = HotResultStore(maxsize=2)
store.put("a", "one")
store.put("b", "two")
store.get("a")
store.put("c", "three")
print("a", store.get("a"))
print("b", store.get("b"))
print("c", store.get("c"))
a one
b None
c three

Async scheduler with bounded concurrency

Business: Unbounded parallel model calls overwhelm backends and inflate bills; max_inflight caps concurrency while metrics expose savings from cache and dedup.

Technical: InferenceScheduler.run checks the hot store, dedup registry, then a semaphore before invoking the async generate callable.

async def generate(snapshot: ConversationSnapshot) -> str:
    await asyncio.sleep(0.001)
    return snapshot.turns[0].messages[0].text

scheduler = InferenceScheduler(
    max_inflight=2,
    generation=GenerationConfig(temperature=0.0),
    store=HotResultStore(),
    dedup=CoalitionDedupRegistry(),
)
jobs = [
    CoalitionJob(
        coalition_key=f"key-{index % 10}",
        snapshot_id=snapshot.snapshot_id,
        snapshot=snapshot,
        absence_policy="delete",
        mask_words=packed.words,
        mask_n_bits=packed.n_bits,
        model_id="mock",
        utility=1.0,
    )
    for index in range(100)
]
metrics = await scheduler.run(jobs, generate)
print(metrics)
SchedulerMetrics(requested=100, executed=10, deduplicated=90, cache_hits=0)