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:
{
"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.
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.
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.
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: a claimed loop is exactly the kind of finding that deserves a durable receipt.
- evidence-binding-compiler: bind a loop's consequences to the events they came from.
- 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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file conditioning-0.1.0.tar.gz.
File metadata
- Download URL: conditioning-0.1.0.tar.gz
- Upload date:
- Size: 78.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e54403b76b056aa5e26fc69280f8a59412505f11966e92417bd40f8ad17b2524
|
|
| MD5 |
55994837463af3819c65aa77c87b5d06
|
|
| BLAKE2b-256 |
da3b7d8ff33a3fbe1bcaa8fa4fb6cc96114d3f03aa8d0b38372d88f3a88e2dbf
|
File details
Details for the file conditioning-0.1.0-py3-none-any.whl.
File metadata
- Download URL: conditioning-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21ddd87ede7d82bf3de59279d1c3506caf6f9f1d46411630265beca10a7919a8
|
|
| MD5 |
882a98b87038a4043456611e0362cb28
|
|
| BLAKE2b-256 |
d7ae8c36525a2534820b2dd9441c8b1031bdff81b19b4e28d52ab9d36d12737c
|