Metadata-Version: 2.3
Name: cambium-ai
Version: 0.3.0
Summary: Cambium — a bounded-agent behavioral coherence engine. The deterministic spine decides what to ask. The model only answers. The eval framework decides whether the answer counted.
License: Apache-2.0
Keywords: ai,agents,agentic-ai,ai-governance,behavioral-finance,evaluation,anthropic,claude,mcp,oakquant
Author: Pumulo Sikaneta
Author-email: pumulo@oakquant.ai
Maintainer: Pumulo Sikaneta
Maintainer-email: pumulo@oakquant.ai
Requires-Python: >=3.13,<4.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Provides-Extra: claude
Provides-Extra: gemini
Provides-Extra: generative
Requires-Dist: PyYAML (>=6.0,<7.0)
Requires-Dist: anthropic (>=0.40,<2.0) ; extra == "claude" or extra == "generative"
Requires-Dist: cryptography (>=46.0.0,<47.0.0)
Requires-Dist: google-genai (>=1.0,<2.0) ; extra == "gemini" or extra == "generative"
Requires-Dist: pydantic (>=2.5.0,<3.0.0)
Project-URL: Bug Tracker, https://github.com/oakquant-ai/cambium/issues
Project-URL: Changelog, https://github.com/oakquant-ai/cambium/blob/main/CHANGELOG.md
Project-URL: Documentation, https://github.com/oakquant-ai/cambium/tree/main/docs
Project-URL: Homepage, https://github.com/oakquant-ai/cambium
Project-URL: Repository, https://github.com/oakquant-ai/cambium
Description-Content-Type: text/markdown

# Cambium

> Cost discipline and bounded agency for LLM call sites — the deterministic spine decides what to ask, the model only answers.

[![PyPI](https://img.shields.io/pypi/v/cambium-ai.svg)](https://pypi.org/project/cambium-ai/)
[![Python](https://img.shields.io/pypi/pyversions/cambium-ai.svg)](https://pypi.org/project/cambium-ai/)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

## What it is

Cambium is the cost-discipline / bounded-agent layer that sits in front of your
model calls. Instead of handing an LLM every decision, it routes work through
the cheapest path that can answer it and only pays for the model when nothing
cheaper will do. Three deterministic, pure-Python capabilities anchor the
public surface:

- **`cambium.distill`** — pre-call payload minimization. Decide whether a
  payload even needs the model (`assemble` / `pass` / `condense` / `summarize`),
  shrink oversized payloads deterministically, and record what every call cost
  via a `CostLedger`.
- **`cambium.resolve`** — a cost-tiered resolution ladder. Try resolvers in cost
  order (deterministic → statistical → generative) and stop at the first one
  confident enough; the expensive generative tier is a budget-gated last resort.
- **`cambium.adapt`** — the feedback substrate for outcome-driven learning:
  record predictions, observe real outcomes, score them, and attribute
  credit/blame across the features and strategies that produced them.

Three further deterministic capabilities carry **accountability** — not "what
did we decide" but "at what epistemic strength, and what could we *not* decide":

- **`cambium.epistemics`** — every claim is typed at construction
  (`theorem` / `identification` / `estimate` / `debt`) and a per-profile **debt
  ledger** records, machine-readably, what the engine does not know and the
  evidence that would resolve it.
- **`cambium.anchoring`** — models a conversation as progressive anchoring of a
  distribution over user intents, with a **monotone commitment fraction** and a
  regime read (exploring / deliberating / drifting). Pure, replayable, shadow-safe.
- **`cambium.conjunction`** — eval discipline: score how many independent,
  zero-tuned predictions one shared config satisfies **at once**, and flag
  per-segment parameters that are fitted on too little data to be falsifiable.

Every one of these is deterministic, dependency-light, and replayable — no model
call, no RNG, no wall clock — so each result is exactly reproducible from its
inputs. See [The science](#the-science) for the grounding.

The package import name is `cambium`; the PyPI distribution is `cambium-ai`.

## Install

```bash
pip install cambium-ai
```

The core (`distill` / `resolve` / `adapt`) has no LLM dependency. Live
generative clients are optional extras:

```bash
pip install "cambium-ai[claude]"   # live Anthropic client
pip install "cambium-ai[gemini]"   # live Gemini generator
```

## `cambium.distill` — pre-call payload minimization + cost ledger

`prepare` runs the full routing pipeline and hands back a ready-to-send payload
plus the accounting the ledger needs. `Route.ASSEMBLE` means no model call at
all.

```python
from cambium.distill import prepare, Route, CostLedger

prepared = prepare(task_output, budget_tokens=2000)
ledger = CostLedger(job_id="research-run")

if prepared.route is Route.ASSEMBLE:
    answer = format_table(task_output)                 # deterministic, no model
    ledger.record_avoided(label="summary", route="assemble",
                          tokens_saved=prepared.tokens_saved)
else:
    response = claude.call(prompt_with(prepared.content))   # your model call
    ledger.record(label="summary", model="claude-3-5-haiku",
                  input_tokens=response.input_tokens,
                  output_tokens=response.output_tokens,
                  tier="generative", tokens_saved=prepared.tokens_saved)

report = ledger.report
report.within_budget(max_cost_usd=0.05)   # cost-regression gate
```

`estimate_tokens(content)` and `condense(content, max_tokens=...)` are also
public if you want the pieces directly.

## `cambium.resolve` — cost-tiered resolution ladder

Implement a `Resolver` per tier and let `resolve` climb only as far as it must.
The generative tier is skipped once the ledger is over budget.

```python
from cambium.resolve import Tier, Resolution, resolve
from cambium.distill import CostLedger

class ExactMatch:
    tier = Tier.DETERMINISTIC
    name = "exact_match"
    def resolve(self, request):
        hit = lookup(request)
        if hit is None:
            return None
        return Resolution(value=hit, confidence=1.0, tier=self.tier)

ledger = CostLedger()
result = resolve(
    request,
    [ExactMatch(), embedding_resolver, llm_resolver],
    ledger=ledger,
    threshold=0.8,
    max_cost_usd=0.05,   # gates the generative tier
)
```

`resolve` returns the first resolution at/above `threshold`, else the best
below-threshold one, else `None`. `ResolverRegistry` is available for grouping
resolvers by decision type.

## `cambium.adapt` — outcome-driven feedback substrate

Record predictions, feed back real outcomes, and mature them into scored
attributions that a reweighting layer can consume.

```python
from cambium.adapt import (
    PredictionRecord, OutcomeRecord, InMemoryPredictionLedger, mature,
)

led = InMemoryPredictionLedger()
led.record_prediction(PredictionRecord(
    id="p1", subject="AAPL", strategy="momentum",
    features={"rsi": 0.7, "trend": 0.3}, predicted=1.0, baseline=0.0,
))
led.record_outcome(OutcomeRecord(prediction_id="p1", actual=1.0))

scores = mature(led)   # [PredictionScore(accuracy=1.0, attributions={...})]
```

`Weights` / `update` / `aggregate` and `predict_from_attributes` /
`blend_strategies` / `best_strategy` close the loop for the reweighting phase.

## `cambium.epistemics` — typed claims + a debt ledger

Provenance stops being "here is what we decided" and becomes "here is what we
decided, **at what epistemic strength**, and here is the itemized list of what we
could not decide and why." Every claim carries a mandatory epistemic type — an
untyped claim cannot be constructed — and open questions are registered as
resolvable debts.

```python
from cambium.epistemics import (
    EpistemicType, TypedClaim, InMemoryEpistemicLedger, evidence_debts,
)

ledger = InMemoryEpistemicLedger()

# Typed at construction: a bounded numeric claim with stated uncertainty.
ledger.record_claim(TypedClaim.build(
    epistemic_type=EpistemicType.ESTIMATE,
    user_id_hash="u1", domain="finance",
    subject="risk_tolerance", value=0.4, uncertainty=0.25,
))

# Register what the engine does NOT know: asserted dimensions the evidence
# does not actually support become debts, each naming its blocker.
evidence_debts(identity, scores, at=window_end, min_events=3, store=ledger)
for debt in ledger.open_debts():
    print(debt.subject, "→", debt.blocker)   # ... "only 1 of 3 events cite it"
```

Debts are append-only and immutable — resolved only by a new `ResolutionEvent`
that references them, so the full lineage (opened → evidence arrived → resolved)
is always queryable. `claims_from_score` / `claims_from_insight` type existing
spine outputs without mutating them.

## `cambium.anchoring` — progressive-anchoring conversation model

A conversation is modeled as a live distribution over user intents that
*contracts* as the user commits. Each event is classified by anchoring power (a
hedge anchors almost nothing; an executed trade anchors hard), and the tracker
emits a monotone commitment fraction plus a provisional regime label.

```python
from cambium.anchoring import (
    IntentDistribution, AnchoringEvent, AnchoringKind, AnchoringTracker,
)

tracker = AnchoringTracker(IntentDistribution.uniform(("save", "invest", "spend")))

for i, kind in enumerate((AnchoringKind.HEDGE,
                          AnchoringKind.STATEMENT,
                          AnchoringKind.EXECUTED_ACTION)):
    snap = tracker.record(
        AnchoringEvent(event_id=f"e{i}", kind=kind, target_intent="invest")
    )
    print(snap.commitment_fraction, snap.top_intent, snap.regime.regime.value)
    # commitment_fraction is monotone non-decreasing across the run, by construction
```

The regime classifier ships **provisional** (`estimate`-typed, dormant) until an
eval harness validates its thresholds; the state tracker is safe to run in shadow
with zero behavior change. `claims_from_snapshot` feeds the anchoring signal into
the epistemic ledger at its honest strength (`estimate`, never fact).

## `cambium.conjunction` — conjunction eval discipline

Report not one averaged metric but how many independent, zero-tuned predictions
the single shared config gets right **simultaneously** — and refuse per-segment
parameters that overfit their own calibration data.

```python
from cambium.conjunction import (
    Prediction, score_conjunction, conjunction_regression_gate,
    SegmentFit, lint_segment_fits,
)

report = score_conjunction([
    Prediction(name="calibration_ece",     passed=True),
    Prediction(name="direction_agreement", passed=True),
    Prediction(name="judge_min",           passed=False),
])
report.conjunction_score          # 0.667 — two of three, one config, zero tuning

# CI gate: fail a change that regresses any conjunct the baseline got right.
conjunction_regression_gate(baseline_report, report)

# Linter: 3 parameters fitted on 3 examples is a restatement, not a fit.
lint_segment_fits([SegmentFit(segment="cohort_a", num_parameters=3,
                              num_calibration_examples=3,
                              beats_shared_on_holdout=True)])   # → flagged circular
```

## The science

These capabilities emerged from translating an interpretive framework into
platform mechanics, but each stands on established, checkable ground — not
metaphor. What follows is the actual math the code runs.

**Epistemic typing.** Claims are partitioned by *how they are known*, forming a
strength ordering: `theorem` (deductively recomputable from config + inputs) →
`identification` (a model-asserted mapping) → `estimate` (a bounded numeric claim
with explicit uncertainty) → `debt` (a known-unknown with a named blocker and a
declared resolution condition). The debt ledger is an explicit, machine-readable
representation of the system's known-unknowns — the epistemic complement to its
outputs. The type is required at construction, so the boundary between what was
*derived* and what was *asserted* can never silently blur.

**Anchoring as entropy contraction.** The unanchored state is a distribution
`p` over intents; its uncertainty is the Shannon entropy `H(p) = -Σ pᵢ ln pᵢ`.
An anchoring event applies an exponential tilt — a tempered pseudo-observation:

```
targeted event → pᵢ  ∝  pᵢ · exp(w·κ·[i = target])
untargeted     → pᵢ  ∝  pᵢ^(1 + w·κ)          (tempering toward the current mode)
```

where `w ∈ [0,1]` is the event's anchoring weight and `κ` a single shared
sharpness constant. This is standard exponential-family / tempered-Bayesian
updating; the "superposition → definite outcome" language is the interpretive
metaphor, but the operator is closed-form and deterministic.

**Commitment fraction — monotone by construction.** Separately from *where* the
intent mass sits, we track *how much* has been committed. Each event commits a
`wₙ` fraction of the remaining unanchored mass:

```
Uₙ = Uₙ₋₁ · (1 − wₙ),   U₀ = 1        (unanchored mass)
Cₙ = 1 − Uₙ                            (commitment fraction)
```

Because every `wₙ ∈ [0,1]`, each factor is in `[0,1]`, so `Uₙ` is non-increasing
and `Cₙ` is monotone non-decreasing — a guarantee that holds for *any* event
sequence, proven by construction rather than clamped after the fact.

**Conjunction over parsimony.** A single averaged score hides compensating
errors. The conjunction score instead counts how many independent, zero-tuned
predictions hold *at once* — jointly passing `N` independent checks is a
multiplicatively stronger claim than any one metric average, so regression gates
key off it. The circularity linter enforces the parsimony discipline behind it:
a per-segment parameter set fitted on comparably-sized calibration data (`k`
parameters to `k` data points) can only restate its inputs — it is unfalsifiable.
The linter requires a minimum data-per-parameter ratio *and* a held-out
improvement over the shared default before per-segment tuning is admitted (an
Occam / model-selection constraint applied to config).

Every formula above is exercised by the test suite and computed with no model
call, so the numbers are exactly reproducible from their inputs.

## License

Apache-2.0. See [LICENSE](LICENSE).

