Metadata-Version: 2.5
Name: conditioning
Version: 0.1.0
Summary: A Conditioning Analyzer that tiers its claims: it detects cue, behavior, consequence, reinforcement loops from event sequences and never asserts a pattern from two ordinary points. Ships episode-dedupe.
Project-URL: Homepage, https://github.com/davedepew/conditioning
Project-URL: Repository, https://github.com/davedepew/conditioning
Author: Dave DePew
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: ai-honesty,audit,behavior,conditioning,governance,llm,patterns
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# Conditioning

The operator's complaint: "it saw the same thing happen twice and now it talks
like it's a law of nature." An AI treats one occurrence as a pattern and
overreacts, or it sees two and declares a habit. Real conditioning does not
work that way, and neither should the machine that reports on it.

A Conditioning Analyzer reads a sequence of events and looks for the loop that
behavioral conditioning is made of: a **cue** leads to a **behavior**, the
behavior has a **consequence**, and the consequence provides **reinforcement**
that makes the behavior more or less likely next time. What makes this analyzer
useful is not that it finds loops. It is what it refuses to do: it never asserts
a pattern from two ordinary points.

## The mechanism

You hand it events. Each event is a dict:

```python
{
  "ts": 1690000000,          # unix seconds, used for ordering and dedupe
  "cue": "deploy alert",     # what preceded the behavior (optional)
  "behavior": "override",    # what was done (required)
  "consequence": "passed",   # what happened after (optional)
  "consequence_class": "money",   # optional; some classes are high-stakes
  "valence": 1.0,            # optional; >0 rewarding, <0 aversive
  "externally_imposed": False,    # optional; was this forced from outside
}
```

The analyzer groups events by cue and behavior into **episodes**, then tiers
each chain. Events can come from logs, ledgers, or receipts. They are injected,
so the engine stays standalone.

```python
from conditioning import analyze

HOUR = 3600
res = analyze([
    {"ts": 0,          "cue": "alert", "behavior": "override", "consequence": "passed"},
    {"ts": 10 * HOUR,  "cue": "alert", "behavior": "override", "consequence": "passed"},
    {"ts": 20 * HOUR,  "cue": "alert", "behavior": "override", "consequence": "passed"},
])

res["loops"][0]["status"]        # 'claimed_loop'  (three distinct episodes)
res["loops"][0]["direction"]     # 'mixed_or_unclear' (no valence was given)
```

## The tiered-claims rule

This is the whole point. A claim is only as loud as the evidence earns.

| Tier | When | What it means |
| --- | --- | --- |
| **noise** | 1 episode | a single occurrence, reported as nothing |
| **candidate_loop** | 2 ordinary episodes | an investigation question ("possible loop?"), never an asserted pattern |
| **claimed_loop** | >= 3 distinct episodes, or >= 2 when the consequence class is high-stakes | a pattern the analyzer is willing to stand behind |

The high-stakes classes are `hard_line_near_miss`, `money`, `public`, and
`class_a`. Two near misses is a claimed loop, because two chances to get hurt is
already a pattern worth naming. Two ordinary passes is only a question.

Even a high-stakes consequence never claims a loop from a single point. One is
always noise.

### Episode dedupe

Repetition inside one sitting is one episode, not a habit. Same cue and behavior
within a 2 hour window collapses to a single episode, unless the consequence
differs. Five rapid identical log rows do not manufacture a claim. This is how
the analyzer avoids the "saw it twice in five minutes, must be a law" failure.

### Direction is never forced

The loop's direction is one of `reward_seeking`, `avoidance`,
`externally_imposed`, or `mixed_or_unclear`. When valence is missing, the answer
is `mixed_or_unclear`, not a guess. A forced direction is a fabricated finding,
so the analyzer declines to pick a side it cannot support.

## Feeding investigations

Detected loops can become investigation questions ("should this loop exist?").
`feed_investigations` drafts them and, if you pass a sink, hands each draft to
it. A failing sink is disclosed, never silently swallowed.

```python
from conditioning import analyze, feed_investigations

fed = feed_investigations(analyze(events))
fed["drafts"][0]["title"]   # "Loop claimed loop: 'alert' -> 'override' x3 ... should this loop exist?"

# with your own queue or ledger as the sink:
fed = feed_investigations(analyze(events), open_investigation=my_queue.open)
```

## The honest metric

The analyzer publishes its **false-positive residual**: the percentage of
claimed loops the reviewer marks wrong in review. That number gets published,
not hidden. `status()` reports the claim rule, the dedupe window, and the
residual metric so the contract is inspectable at runtime.

```python
from conditioning import status
status()["claim_rule"]        # ">=3 distinct episodes, or >=2 high-stakes"
```

## Install

```
pip install conditioning
```

Zero dependencies, standard library only. Bring your own event source and, if
you want it, your own investigation sink.

## Fits with

Part of **The Operator's Honesty Stack**, open-source honesty engines from the
same production system, composed, never merged:

- [decision-receipts](https://github.com/davedepew/decision-receipts): a
  claimed loop is exactly the kind of finding that deserves a durable receipt.
- [evidence-binding-compiler](https://github.com/davedepew/evidence-binding-compiler):
  bind a loop's consequences to the events they came from.
- [capability-honesty](https://github.com/davedepew/capability-honesty): a
  claimed loop is a claim; a candidate loop is still just a question.

## License

Apache 2.0. Copyright 2026 Dave DePew Enterprises, Inc.
