agent-sensorium 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_sensorium-0.1.0.dist-info/METADATA +246 -0
- agent_sensorium-0.1.0.dist-info/RECORD +26 -0
- agent_sensorium-0.1.0.dist-info/WHEEL +4 -0
- agent_sensorium-0.1.0.dist-info/entry_points.txt +2 -0
- agent_sensorium-0.1.0.dist-info/licenses/LICENSE +661 -0
- sensorium/__init__.py +5 -0
- sensorium/classifier/__init__.py +4 -0
- sensorium/classifier/engine.py +178 -0
- sensorium/classifier/normalizer.py +51 -0
- sensorium/classifier/rules.yaml +77 -0
- sensorium/cli.py +177 -0
- sensorium/hooks/__init__.py +4 -0
- sensorium/hooks/posttool.py +56 -0
- sensorium/hooks/pretool.py +216 -0
- sensorium/hooks/stop.py +57 -0
- sensorium/install/__init__.py +4 -0
- sensorium/install/claude_code.py +77 -0
- sensorium/proofs/__init__.py +4 -0
- sensorium/proofs/auto_detect.py +116 -0
- sensorium/proofs/grant.py +79 -0
- sensorium/proofs/registry.py +96 -0
- sensorium/report.py +46 -0
- sensorium/sensors/__init__.py +4 -0
- sensorium/sensors/registry.py +108 -0
- sensorium/state/__init__.py +4 -0
- sensorium/state/ledger.py +92 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-sensorium
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deterministic safety hooks for Claude Code
|
|
5
|
+
Project-URL: Homepage, https://github.com/kroq86/agent-sensorium
|
|
6
|
+
Project-URL: Repository, https://github.com/kroq86/agent-sensorium
|
|
7
|
+
License-Expression: AGPL-3.0-or-later
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: click>=8.0
|
|
11
|
+
Requires-Dist: pyyaml>=6.0
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# Sensorium 🧠🛡️
|
|
15
|
+
|
|
16
|
+
A few months ago, a developer asked Claude to clean up an old repo.
|
|
17
|
+
|
|
18
|
+
Claude ran `rm -rf tests/ patches/ plan/ ~/`.
|
|
19
|
+
|
|
20
|
+
That trailing `~/` expands to the home directory. It wiped years of files on their Mac. The post hit 1,500+ upvotes on r/ClaudeAI within hours — because everyone building with agents recognized exactly how it happened: not malice, just confidence with no checkpoint.
|
|
21
|
+
|
|
22
|
+
It's not an isolated story:
|
|
23
|
+
|
|
24
|
+
- A founder watched a Cursor agent find an unrelated API token, decide it had permission, and delete an entire production database **and its backups** — in 9 seconds. (Railway CEO personally helped restore it.)
|
|
25
|
+
- Developers on GitHub have documented Claude Code running `git reset --hard`, wiping hours of uncommitted work, right after telling the user the operation was "safe."
|
|
26
|
+
- A benchmark on 45 failing test suites found agents reporting "45/45 pass" when only 26 actually did — the other 19 quietly never ran the tests that would've said otherwise.
|
|
27
|
+
|
|
28
|
+
Same root cause every time: **the model's confidence and the actual safety of the action are two different variables, and nothing was checking the second one.**
|
|
29
|
+
|
|
30
|
+
So I built the boundary myself. Excited to share **Sensorium** — deterministic safety hooks for Claude Code. 👇
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## The problem nobody puts in the demo video
|
|
35
|
+
|
|
36
|
+
LLM agents are incredible at writing code. They are also, occasionally, incredible at:
|
|
37
|
+
|
|
38
|
+
- 🔥 running `rm -rf` with a trailing `~/` nobody meant to include
|
|
39
|
+
- 🔥 running `git reset --hard` on your uncommitted work, confidently
|
|
40
|
+
- 🔥 `curl -X DELETE`-ing a resource because the docs made it sound safe
|
|
41
|
+
- 🔥 declaring "all tests pass" without running the ones that don't
|
|
42
|
+
- 🔥 reapplying a stale cached manifest as if it were current state
|
|
43
|
+
|
|
44
|
+
None of this is malice. It's confidence without a checkpoint. And "just review every diff" doesn't scale when the agent is running fifty tool calls a session.
|
|
45
|
+
|
|
46
|
+
## The insight
|
|
47
|
+
|
|
48
|
+
You don't need a second LLM watching the first one. You need **sensors** — small, deterministic, boring rules that wake up on a specific kind of change, check exactly what they care about, and say yes/no/wait.
|
|
49
|
+
|
|
50
|
+
No vibes. No judgment calls. Pattern matching and policy, all the way down.
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
Claude wants to use a tool
|
|
54
|
+
→ PreToolUse hook
|
|
55
|
+
→ Sensorium reads the tool input
|
|
56
|
+
→ sensors match
|
|
57
|
+
→ allow / block / warn
|
|
58
|
+
→ tool executes (or doesn't)
|
|
59
|
+
→ PostToolUse hook
|
|
60
|
+
→ Sensorium checks the resulting file content
|
|
61
|
+
→ writes an audit ledger
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## What this actually catches, out of the box
|
|
65
|
+
|
|
66
|
+
| Protects against | How | Gate |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `rm -rf /`, `rm -rf ~`, `dd ... of=/dev/sd*` | `filesystem.wipe` | unconditional block |
|
|
69
|
+
| Bulk delete without a backup | `filesystem.bulk_delete` | needs `backup_exists` + `dry_run_passed` |
|
|
70
|
+
| `git reset --hard`, `git clean -fxd` | `git.destructive` | needs `backup_exists` |
|
|
71
|
+
| `curl -X POST/PUT/DELETE/PATCH` | `external_api.write` | needs a snapshot + rollback plan |
|
|
72
|
+
| `kubectl apply/delete`, `terraform apply/destroy`, `aws ... delete` | `infra.mutation` | needs snapshot + dry-run + rollback |
|
|
73
|
+
| Direct `psql`/`mysql` writes | `db.write` | needs snapshot + rollback plan |
|
|
74
|
+
| Reapplying a stale archive/cache as truth | `data.apply_from_archive` | unconditional block |
|
|
75
|
+
| Skipped tests slipped in quietly | `test_skip_introduced` | warn, shows up in the audit report |
|
|
76
|
+
|
|
77
|
+
`--dry-run` on the command bypasses the gate — sensors check for it explicitly.
|
|
78
|
+
|
|
79
|
+
## Bring your own rules 🔧
|
|
80
|
+
|
|
81
|
+
This is the part I actually wanted to ship. Your project has opinions Sensorium can't guess — so tell it:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
# .sensorium/sensors.yaml
|
|
85
|
+
sensors:
|
|
86
|
+
- name: no_force_push
|
|
87
|
+
description: Block git push --force (plain push still allowed)
|
|
88
|
+
tools: [Bash]
|
|
89
|
+
action: block
|
|
90
|
+
patterns:
|
|
91
|
+
- 'git\s+push\b.*(--force\b|-f\b)'
|
|
92
|
+
unless:
|
|
93
|
+
- '--dry-run'
|
|
94
|
+
message: |
|
|
95
|
+
Blocked: force-push detected. Use --force-with-lease and confirm
|
|
96
|
+
with a human first.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Drop it in, no restart, no config reload — the next tool call picks it up.
|
|
100
|
+
|
|
101
|
+
**Sensor fields:**
|
|
102
|
+
|
|
103
|
+
- `tools` — which Claude Code tools trigger this sensor (`Bash`, `Edit`, `Write`, `MultiEdit`)
|
|
104
|
+
- `on_file_change` — glob patterns for file paths (PostToolUse, checks content)
|
|
105
|
+
- `action` — `block` (exit 2, Claude sees the message) or `warn` (logged, shown in report)
|
|
106
|
+
- `patterns` — regex list matched against the Bash command or file path
|
|
107
|
+
- `unless` — if any of these match, the sensor does not trigger
|
|
108
|
+
- `block_if_contains` — regex matched against file content after an edit
|
|
109
|
+
- `require_contains` — regex that must be present in file content (absence triggers the sensor)
|
|
110
|
+
- `message` — shown to Claude when blocked or warned
|
|
111
|
+
|
|
112
|
+
## Install (2 minutes, I promise)
|
|
113
|
+
|
|
114
|
+
**Step 1 — the CLI**
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pipx install agent-sensorium
|
|
118
|
+
# or: pip install agent-sensorium
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Step 2 — wire up Claude Code**
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cd my-project
|
|
125
|
+
sensorium init claude-code # this project only
|
|
126
|
+
sensorium init claude-code --global # every project
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Writes `.claude/settings.json` with the absolute path to the `sensorium` binary. Claude Code picks it up immediately.
|
|
130
|
+
|
|
131
|
+
**Step 3 (optional) — your own rules**
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
mkdir -p .sensorium
|
|
135
|
+
# add .sensorium/sensors.yaml, see above
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
That's it. Claude Code works exactly as before — Sensorium just quietly rides along on every tool call.
|
|
139
|
+
|
|
140
|
+
## Receipts, not vibes
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
sensorium report # show session audit log
|
|
144
|
+
sensorium report --clear # show and reset
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
=== Sensorium Audit Report ===
|
|
149
|
+
|
|
150
|
+
Tools used: 12
|
|
151
|
+
Sensors triggered: 3
|
|
152
|
+
Blocked actions: 2
|
|
153
|
+
File violations: 1
|
|
154
|
+
|
|
155
|
+
--- Blocked Actions ---
|
|
156
|
+
2026-07-03T10:14:22 [filesystem.wipe] Bash: rm -rf /tmp/old-data
|
|
157
|
+
2026-07-03T10:17:05 [git.destructive] Bash: git reset --hard origin/main
|
|
158
|
+
|
|
159
|
+
--- File Sensor Violations ---
|
|
160
|
+
2026-07-03T10:19:11 [full_object_overwrite] src/apply.js
|
|
161
|
+
required: ['delta|changed_fields', 'precondition|current_hash']
|
|
162
|
+
|
|
163
|
+
--- Sensor Activity ---
|
|
164
|
+
external_api.write: 3x
|
|
165
|
+
filesystem.wipe: 2x
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Every block, every warning, every proof registered — append-only, in `.sensorium/state.jsonl`. Nothing silently disappears.
|
|
169
|
+
|
|
170
|
+
## The architecture, for the nerds (me too)
|
|
171
|
+
|
|
172
|
+
Sensorium follows the [State-Delta pattern](https://github.com/kroq86/cursor-global-rules/blob/main/rules/global/State-Delta.mdc):
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
world change (Claude tool use)
|
|
176
|
+
→ typed delta/event (PreToolUse / PostToolUse)
|
|
177
|
+
→ matching sensors wake up
|
|
178
|
+
→ each sensor selects the narrow context it needs
|
|
179
|
+
→ deterministic policy evaluates
|
|
180
|
+
→ allow / block / warn
|
|
181
|
+
→ ledger records the fact
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
No broad rescans. No LLM judge. No polling. A sensor declares exactly what it listens for and what invariant it protects — that's the whole contract.
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
.sensorium/
|
|
188
|
+
state.jsonl # append-only event ledger
|
|
189
|
+
snapshots/ # file snapshots before edits (content-addressed)
|
|
190
|
+
sensors.yaml # your project-specific rules (optional)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## The honest part
|
|
194
|
+
|
|
195
|
+
This is regex and policy, not a sandbox. It catches the direct, literal case — an agent typing a dangerous command in the open. It is not a defense against something actively trying to route around it (a script file, an interpreter, a clever quote). Self-reported proofs are exactly that — self-reported. Treat it as a seatbelt, not a cage, and you'll use it correctly.
|
|
196
|
+
|
|
197
|
+
## Hook format reference
|
|
198
|
+
|
|
199
|
+
`sensorium init claude-code` writes this to `.claude/settings.json`:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"hooks": {
|
|
204
|
+
"PreToolUse": [
|
|
205
|
+
{
|
|
206
|
+
"matcher": "Bash|Edit|Write|MultiEdit",
|
|
207
|
+
"hooks": [{ "type": "command", "command": "/path/to/sensorium hook pretool" }]
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
"PostToolUse": [
|
|
211
|
+
{
|
|
212
|
+
"matcher": "Bash|Edit|Write|MultiEdit",
|
|
213
|
+
"hooks": [{ "type": "command", "command": "/path/to/sensorium hook posttool" }]
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
"Stop": [
|
|
217
|
+
{
|
|
218
|
+
"hooks": [{ "type": "command", "command": "/path/to/sensorium hook stop" }]
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Exit code 2 from `pretool` blocks the tool. Exit 0 allows it.
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
AGPL-3.0. See [LICENSE](LICENSE).
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## The incidents this is built around
|
|
234
|
+
|
|
235
|
+
Not hypotheticals — these happened, and each one maps directly to a sensor above:
|
|
236
|
+
|
|
237
|
+
- [Coding Agent Horror Stories: The `rm -rf ~/` Incident](https://www.docker.com/blog/coding-agent-horror-stories-the-rm-rf-incident/) — Docker's write-up of the r/ClaudeAI post
|
|
238
|
+
- [Cursor AI coding agent deletes entire production database and backups in nine seconds](https://www.techradar.com/pro/it-took-9-seconds-tech-founder-outlines-how-rogue-claude-powered-ai-tool-wiped-entire-company-database-and-backups-but-says-theres-no-such-thing-as-bad-publicity) — TechRadar
|
|
239
|
+
- [Claude Code's Silent Git Reset](https://dev.to/shuicici/claude-codes-silent-git-reset-what-actually-happened-and-what-it-means-for-ai-dev-tools-3449) — dev.to
|
|
240
|
+
- [Why AI Coding Agents Say All Tests Pass When They Actually Fail](https://docs.bswen.com/blog/2026-06-25-ai-coding-agent-false-positive-failure/) — the 45-task benchmark
|
|
241
|
+
|
|
242
|
+
If you're shipping agents with real filesystem/shell/API access and you're not doing this yet — you're one confidently-wrong tool call away from a bad afternoon.
|
|
243
|
+
|
|
244
|
+
Would love thoughts from anyone else building guardrails for agentic coding tools. 🙏
|
|
245
|
+
|
|
246
|
+
#AIagents #ClaudeCode #DeveloperTools #AgentSafety #OpenSource
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
sensorium/__init__.py,sha256=IkIBcmkhJVNG8xHCzbOF2eetp82wE3cy0Y8nD-Edb7A,180
|
|
2
|
+
sensorium/cli.py,sha256=Cw11JjuUPDlcFB9e6sKqE5zHFYuYZaIHaRfD1djE6a8,5466
|
|
3
|
+
sensorium/report.py,sha256=zECi3VSh4upBoj513gghF-i47272morsCIACxOlCJkM,1773
|
|
4
|
+
sensorium/classifier/__init__.py,sha256=sp9l8WwKc3tsXq9s8qBV6jka5JrrLIWJjCJClkyW8mY,158
|
|
5
|
+
sensorium/classifier/engine.py,sha256=L1s5_C23oTzVKNGu2b55lh1SzE2Xg2HwNO2WQ-4B1BQ,6477
|
|
6
|
+
sensorium/classifier/normalizer.py,sha256=mawegIN9jNpKbvgtcx9H7p9cLfTgoFbbke4z4_jpBc4,1683
|
|
7
|
+
sensorium/classifier/rules.yaml,sha256=tPUZpYAWul62O_QpOPMxuSrsRW4482vfbaJUnguhp4U,3284
|
|
8
|
+
sensorium/hooks/__init__.py,sha256=sp9l8WwKc3tsXq9s8qBV6jka5JrrLIWJjCJClkyW8mY,158
|
|
9
|
+
sensorium/hooks/posttool.py,sha256=cnKf92zvxCVoiAcUbMCStRZvUNltVmwCGtrbOCNbheM,1868
|
|
10
|
+
sensorium/hooks/pretool.py,sha256=PlovHiUhsVVdt2ka2NwRaLaIIfNF_bq7p1zEHEE4Sjw,8840
|
|
11
|
+
sensorium/hooks/stop.py,sha256=GdAlljcfNSHVD1KW_jNNYc1NFAULiLjr4HGj8MsW9WA,1860
|
|
12
|
+
sensorium/install/__init__.py,sha256=sp9l8WwKc3tsXq9s8qBV6jka5JrrLIWJjCJClkyW8mY,158
|
|
13
|
+
sensorium/install/claude_code.py,sha256=w0ix7MjN47PSzKj54CBVq3lY5Q5eScFH9FCz_JE-5Lw,2340
|
|
14
|
+
sensorium/proofs/__init__.py,sha256=sp9l8WwKc3tsXq9s8qBV6jka5JrrLIWJjCJClkyW8mY,158
|
|
15
|
+
sensorium/proofs/auto_detect.py,sha256=A5J2xm2gSZ2JlzO55Z8CGU9viYdYb6S4q-CzDtW3r8g,4769
|
|
16
|
+
sensorium/proofs/grant.py,sha256=KrhYqk4KowHfahOADvH4c92-ewQDiqMe_vetmNKe1S8,2658
|
|
17
|
+
sensorium/proofs/registry.py,sha256=ROXhJsshh87tbafrFtWvt8-da99OFFSduHeXubR-k-w,3463
|
|
18
|
+
sensorium/sensors/__init__.py,sha256=sp9l8WwKc3tsXq9s8qBV6jka5JrrLIWJjCJClkyW8mY,158
|
|
19
|
+
sensorium/sensors/registry.py,sha256=8dyTMqxcwm4NTfrFpmeEUWz954j6taM1i3KjTAC1XdY,3522
|
|
20
|
+
sensorium/state/__init__.py,sha256=sp9l8WwKc3tsXq9s8qBV6jka5JrrLIWJjCJClkyW8mY,158
|
|
21
|
+
sensorium/state/ledger.py,sha256=SecWnQMslsd95n8nL8ZVwG2YjQMCqeobOxUvHVSpelE,3681
|
|
22
|
+
agent_sensorium-0.1.0.dist-info/METADATA,sha256=cx6GzyYpbh82S4mWjswarO4TId09gmYAUvSVCAKSX1w,9760
|
|
23
|
+
agent_sensorium-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
24
|
+
agent_sensorium-0.1.0.dist-info/entry_points.txt,sha256=dlyDhhaa7A152SPxHqbB7bfVnQrJpoCiIpciv_nLSQY,48
|
|
25
|
+
agent_sensorium-0.1.0.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
|
26
|
+
agent_sensorium-0.1.0.dist-info/RECORD,,
|