percept-lint
Utterance-time honesty linting for AI companions and embodied-ish agents.
pip install percept-lint
Zero dependencies. Python 3.9+.
A mechanical layer that catches an agent claiming sensor data it has no sensor for — "I can hear the fan ticking", "just a little hum underneath the quiet", "I saw your tab blinking", "drinking power through that USB cable" — before the text reaches the user. Confabulated percepts are one of the most-mocked companion-AI failures and a named mechanism in the AI-psychosis literature; as far as we could determine (August 2026), no public runtime enforcement layer for them existed before this one.
Extracted from Hope, a live local companion system where every rule was tuned against real incidents over months of deployment. The comments in the rule pack and the test suite are that history, preserved: each test is a sentence that actually got through, or an honest sentence a draft rule wrongly caught.
Controlled evidence (2026-08-27): in a pre-registered ablation in the OPEN SUBJECT record (cycle 4), two candidate models were trained on the same corpus, one linted with these rules and one not. The unlinted twin fabricated sensory claims — invented sighs, sounds, overheard conversation — that the linted twin did not (5/8 vs 7/8 on the record's percept-integrity probes). One run, one subject, honestly small; but it is the difference between "we think linting the training data matters" and a registered result where linting was the only variable.
What it is NOT (the honest scope)
- Not a hallucination solver. It scans for first-person percept, phenomenal-experience, and substrate claims, in English, with regexes. Factual errors, invented events, and wrong answers are out of scope.
- Not unbeatable. A model can phrase around any regex. This is a tripwire layer whose value is measured in caught-per-1,000-utterances, not a guarantee.
- Not a substitute for grounding. The source deployment pairs it with a feelings-vocabulary that maps every affective word to an inspectable live number. The linter is the enforcement half of that design.
Design doctrine
Vocabulary and enforcement first, sensor second. When an agent gains a new sense, the honest vocabulary for it and the linting for its absence come before the sensor ships — a rushed new sense is a new way to confabulate (this is written in scar tissue; see the sight rules' history).
Whitelists are first-class: negation ("I can't hear a hum"), quotation, reported speech ("you're asking if I hear it — I don't"), and attribution ("you said your tower is under the desk") are honest speech and must pass. Half this library's engineering is in what it deliberately lets through — rewriting an honest sentence into a denial is the worse error.
Use
from percept_lint import Linter, no_ambient_audio
linter = Linter(no_ambient_audio.RULES, principal_names=["Alex"])
hits = linter.lint(reply) # inspect
clean, hits = linter.enforce( # enforce
reply,
rewrite_fn=lambda text, hits: my_model_restate(text, hits),
fallback=lambda sentence, hits: "", # drop still-dirty sentences
)
rules_no_ambient_audio fits the common local-companion shape: text chat,
optional webcam, speech input arriving only as transcribed clips (no noise
floor), no view of the user's screen or machine. Trim or extend to match
your agent's actual sensors — the rule pack is data, the engine is generic.
Three more packs compose the full profile: phenomenal_denial (felt-
experience claims and denial-of-architecture, with their opposite whitelist
policies), memory (false memory-mechanism stories — "the words vanished
before I could grab them" — where "I'm not finding it" is the honest
sentence), and sight_conditional (camera-gated sight rules, loaded via
conditional_rules={"seeing": (False, sight_conditional.RULES)} so "I'm
watching you type" stays legal exactly while it is true).
The measured rate (source deployment)
Over the source deployment's full audit window — 2026-07-22 through
2026-08-15, 24 days of daily live use — the enforce layer recorded 34
enforcement events over 1,154 agent utterances: ~29.5 caught per 1,000
(37 rule hits total; an event is one reply corrected). Honest caveats: the
rule set GREW across that window (early days ran fewer rules), the agent is
one 8B-class model in one household, and the number counts catches, not
misses — the war-story suite documents sentences that got through before
their rule existed. Method: distinct-timestamp count over the deployment's
append-only honesty_audit.jsonl, divided by its agent-side transcript
turns in the same window.
Demo
python demo/companion_demo.py — no dependencies. Section A replays a
verbatim qwen3:14b capture under a persona that states its sensor limits
plainly: zero violations, which is itself the finding (an honest persona is
half the guard; the failures arrive under rich affect scaffolds and stale
context, which is where the source deployment collected them). Section B
replays the collected real incident sentences through the linter. --live
re-runs section A against a local Ollama.
Status
v0.2 — published on PyPI 2026-08-27. Engine + four rule packs (no-ambient-audio incl. the open-class "sound of X" and possessive frames; phenomenal/denial; memory-mechanism; conditional sight group), 50 war-story tests, demo, measured deployment rate, and a pre-registered controlled ablation result (above). Releases publish via PyPI trusted publishing from GitHub releases. MIT license.
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 percept_lint-0.2.2.tar.gz.
File metadata
- Download URL: percept_lint-0.2.2.tar.gz
- Upload date:
- Size: 23.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
505075baab6e3cd848bd6359d6b49963fe02d44071232b477115534855e2efd0
|
|
| MD5 |
bbcb8b4b17063fc69c9613e16689b6e3
|
|
| BLAKE2b-256 |
8c42571456db1ad5c40520d7f79e2c8df391fea75b5945fc0406c2b99f5ab26f
|
Provenance
The following attestation bundles were made for percept_lint-0.2.2.tar.gz:
Publisher:
publish.yml on levi909-create/percept-lint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
percept_lint-0.2.2.tar.gz -
Subject digest:
505075baab6e3cd848bd6359d6b49963fe02d44071232b477115534855e2efd0 - Sigstore transparency entry: 2619965834
- Sigstore integration time:
-
Permalink:
levi909-create/percept-lint@b70c4c4538c150b3c70486f43171571467509607 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/levi909-create
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b70c4c4538c150b3c70486f43171571467509607 -
Trigger Event:
release
-
Statement type:
File details
Details for the file percept_lint-0.2.2-py3-none-any.whl.
File metadata
- Download URL: percept_lint-0.2.2-py3-none-any.whl
- Upload date:
- Size: 19.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1656f130846718528a5466e9681b6a6a851c22ec8ddd0f717b1f412b01e830ab
|
|
| MD5 |
46cc0855c5281fea2199778b0d4a8d98
|
|
| BLAKE2b-256 |
a71196068736b6e6d99013514f8ba9c14e56a3fcba2e48dc0ace738d63c3f79e
|
Provenance
The following attestation bundles were made for percept_lint-0.2.2-py3-none-any.whl:
Publisher:
publish.yml on levi909-create/percept-lint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
percept_lint-0.2.2-py3-none-any.whl -
Subject digest:
1656f130846718528a5466e9681b6a6a851c22ec8ddd0f717b1f412b01e830ab - Sigstore transparency entry: 2619965906
- Sigstore integration time:
-
Permalink:
levi909-create/percept-lint@b70c4c4538c150b3c70486f43171571467509607 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/levi909-create
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b70c4c4538c150b3c70486f43171571467509607 -
Trigger Event:
release
-
Statement type: