summaryrefslogtreecommitdiff
path: root/constrain
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-24 11:39:36 -0300
committerGabriel Schneider <[email protected]>2026-07-30 15:18:55 -0300
commitcb05c363045bf0e7af5858f6d7bc26f026fa9d70 (patch)
treec3e89426ff044ffe339dd3a77e7f3b7115cba636 /constrain
downloadnotevi-cb05c363045bf0e7af5858f6d7bc26f026fa9d70.tar.gz
notevi-cb05c363045bf0e7af5858f6d7bc26f026fa9d70.zip
Diffstat (limited to 'constrain')
-rw-r--r--constrain/AGENTS.md11
-rw-r--r--constrain/README.txt64
-rw-r--r--constrain/claude-headless-settings.json19
-rw-r--r--constrain/claude-settings.json19
-rw-r--r--constrain/codex-config.toml12
-rwxr-xr-xconstrain/vr-only-guard.sh19
-rw-r--r--constrain/vr-only.rules44
7 files changed, 188 insertions, 0 deletions
diff --git a/constrain/AGENTS.md b/constrain/AGENTS.md
new file mode 100644
index 0000000..abec095
--- /dev/null
+++ b/constrain/AGENTS.md
@@ -0,0 +1,11 @@
+# Reading code here
+
+Files are read with the `vr` tool — run `vr -doc` once for full usage.
+
+- `vr read FILE:START-END` — line-numbered read; prefer ranges over whole files
+- `vr grep PATTERN [PATH]` — regex search; scope with a path
+- `vr read -r REV FILE`, `vr grep -r REV PATTERN PATH` — at another jj change
+- `vr note -f FILE:10-42 -t struct TEXT...` — record observations as you read
+
+Navigate top-down: grep for the symbol, read the enclosing range, note what
+you learn. Reads and notes are keyed to the current jj change automatically.
diff --git a/constrain/README.txt b/constrain/README.txt
new file mode 100644
index 0000000..01657a8
--- /dev/null
+++ b/constrain/README.txt
@@ -0,0 +1,64 @@
+Constrain agents to read files only through vr — enforcement comes from
+harness config; AGENTS.md is navigation guidance only, never the constraint.
+
+── files here ──────────────────────────────────────────────────────────────
+claude-settings.json project install: <repo>/.claude/settings.json
+ (hook path uses $CLAUDE_PROJECT_DIR)
+claude-headless-settings.json no-install variant for `claude -p --settings`
+ (hook path is absolute into this dir)
+vr-only-guard.sh PreToolUse hook: blocks cat/rg/sed/... and
+ jj/git content reads; its error message points
+ the agent at `vr -doc`, so agents converge
+ even with zero instructions
+vr-only.rules codex execpolicy rules (allow vr, forbid readers)
+codex-config.toml optional: centralize VR_LOG for codex
+AGENTS.md how-to-navigate-with-vr guidance for the repo
+
+── one-time setup ──────────────────────────────────────────────────────────
+put vr on PATH: go build -o ~/.local/bin/vr . (repo root, not vrsite/)
+
+── running a constrained CLAUDE investigation ──────────────────────────────
+No repo mutation needed; from the target repo dir:
+
+ VR_LOG=/abs/path/trace.jsonl \
+ claude -p --settings /Users/goblin/00-projects/0x4200.cafe/vr-agent-logger/constrain/claude-headless-settings.json \
+ --allowedTools 'Bash(vr)' 'Bash(vr:*)' \
+ < prompt.txt > report.md
+
+ - --allowedTools on the CLI is required headless: allow rules inside
+ settings are IGNORED until the workspace is trusted (deny rules and the
+ hook always apply). Interactive use instead: install claude-settings.json
+ + hook into the repo's .claude/, open once, accept the trust dialog.
+ - put the prompt on stdin; a positional prompt after --allowedTools gets
+ eaten by the flag's list parsing.
+
+── running a constrained CODEX investigation ───────────────────────────────
+ cp vr-only.rules ~/.codex/rules/ # activate (GLOBAL: constrains
+ # every codex session while there)
+ VR_LOG=/abs/path/trace.jsonl \
+ codex exec -s danger-full-access "$(cat prompt.txt)" > report.md
+ rm ~/.codex/rules/vr-only.rules # deactivate when done
+
+ - danger-full-access is required: workspace-write blocks .git/ writes,
+ which kills jj's working-copy snapshot and with it `vr read`.
+ - enforcement is pre-exec by codex's execpolicy engine, even through
+ `zsh -lc` wrappers; validate rules with:
+ codex execpolicy check --rules vr-only.rules -- cat foo.txt
+
+── shared trace + rendering ────────────────────────────────────────────────
+ - VR_LOG must point at a FILE path whose parent exists. If the path is a
+ directory (or becomes one), agents improvise their own log files and you
+ will be merging jsonl afterwards. Ask me how I know.
+ - Multiple agents may share one log: note ids are per-session, appends are
+ line-atomic. Same file = one merged timeline for free.
+ - In the prompt, tell the agent to leave pinned notes
+ (vr note -f FILE:START-END -t kind "...") — that is the payload.
+ - Render the trace afterwards:
+ vrsite/vrsite -log trace.jsonl -repo <repo> -out site -title "..."
+
+── known holes (accepted) ──────────────────────────────────────────────────
+Scripting runtimes (python/node/perl) can still open files — uncomment their
+rules in vr-only.rules / extend the hook to close, at the cost of breaking
+legitimate scripts. Neither harness constrains its own non-shell internals
+beyond what the deny rules cover. Codex's rules file is global-only; there is
+no per-project rules mechanism (probed, none exists as of codex 0.145).
diff --git a/constrain/claude-headless-settings.json b/constrain/claude-headless-settings.json
new file mode 100644
index 0000000..f125ed2
--- /dev/null
+++ b/constrain/claude-headless-settings.json
@@ -0,0 +1,19 @@
+{
+ "permissions": {
+ "deny": ["Read", "Grep", "Glob"],
+ "allow": ["Bash(vr)", "Bash(vr:*)"]
+ },
+ "hooks": {
+ "PreToolUse": [
+ {
+ "matcher": "Bash",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "/Users/goblin/00-projects/0x4200.cafe/vr-agent-logger/constrain/vr-only-guard.sh"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/constrain/claude-settings.json b/constrain/claude-settings.json
new file mode 100644
index 0000000..524ccbd
--- /dev/null
+++ b/constrain/claude-settings.json
@@ -0,0 +1,19 @@
+{
+ "permissions": {
+ "deny": ["Read", "Grep", "Glob"],
+ "allow": ["Bash(vr)", "Bash(vr:*)"]
+ },
+ "hooks": {
+ "PreToolUse": [
+ {
+ "matcher": "Bash",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/vr-only-guard.sh"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/constrain/codex-config.toml b/constrain/codex-config.toml
new file mode 100644
index 0000000..fa5902f
--- /dev/null
+++ b/constrain/codex-config.toml
@@ -0,0 +1,12 @@
+# Optional: merge into ~/.codex/config.toml (or pass per-run with -c).
+# Enforcement lives in vr-only.rules (~/.codex/rules/); this only centralizes
+# the log and, if you want, tightens approvals for everything unmatched.
+
+# approval_policy = "untrusted"
+
+[shell_environment_policy]
+inherit = "all"
+
+[shell_environment_policy.set]
+# one shared vr log for every repo codex touches
+VR_LOG = "/Users/goblin/00-projects/0x4200.cafe/vr-agent-logger/vr-log.jsonl"
diff --git a/constrain/vr-only-guard.sh b/constrain/vr-only-guard.sh
new file mode 100755
index 0000000..0de698f
--- /dev/null
+++ b/constrain/vr-only-guard.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+# Claude Code PreToolUse hook (matcher: Bash): deny shell commands that read
+# files without going through vr. A guardrail, not a jail — it catches the
+# common readers at command position, not every conceivable bypass.
+cmd=$(jq -r '.tool_input.command // empty')
+
+readers='cat|head|tail|less|more|sed|awk|cut|rg|grep|egrep|fgrep|find|fd|strings|xxd|hexdump|od|tac|nl'
+pattern='(^|[;&|(`]|\$\()[[:space:]]*('$readers')([[:space:]]|$)'
+
+if printf '%s' "$cmd" | grep -qE "$pattern"; then
+ echo "blocked: read/search files only through vr (run 'vr -doc' for usage)" >&2
+ exit 2
+fi
+vcs='jj[[:space:]]+(file[[:space:]]+show|diff)|git[[:space:]]+(show|diff|grep|cat-file|blame|log)'
+if printf '%s' "$cmd" | grep -qE "(^|[;&|(\`])[[:space:]]*($vcs)"; then
+ echo "blocked: use 'vr read -r REV FILE' / 'vr grep -r REV' instead of raw jj/git reads" >&2
+ exit 2
+fi
+exit 0
diff --git a/constrain/vr-only.rules b/constrain/vr-only.rules
new file mode 100644
index 0000000..54d3392
--- /dev/null
+++ b/constrain/vr-only.rules
@@ -0,0 +1,44 @@
+# codex execpolicy: force file reading through vr (which logs every read).
+# Activate: cp vr-only.rules ~/.codex/rules/
+# Deactivate: rm ~/.codex/rules/vr-only.rules
+# The engine parses through `/bin/zsh -lc '...'` wrappers, so these match the
+# inner command. Editing tools (apply_patch) are unaffected — this only
+# constrains reading. Known hole: scripting runtimes can still open files;
+# uncomment the last block to close it at the cost of breaking legit scripts.
+
+prefix_rule(pattern=["vr"], decision="allow")
+
+prefix_rule(pattern=["cat"], decision="forbidden")
+prefix_rule(pattern=["head"], decision="forbidden")
+prefix_rule(pattern=["tail"], decision="forbidden")
+prefix_rule(pattern=["less"], decision="forbidden")
+prefix_rule(pattern=["more"], decision="forbidden")
+prefix_rule(pattern=["sed"], decision="forbidden")
+prefix_rule(pattern=["awk"], decision="forbidden")
+prefix_rule(pattern=["cut"], decision="forbidden")
+prefix_rule(pattern=["rg"], decision="forbidden")
+prefix_rule(pattern=["grep"], decision="forbidden")
+prefix_rule(pattern=["egrep"], decision="forbidden")
+prefix_rule(pattern=["fgrep"], decision="forbidden")
+prefix_rule(pattern=["find"], decision="forbidden")
+prefix_rule(pattern=["fd"], decision="forbidden")
+prefix_rule(pattern=["strings"], decision="forbidden")
+prefix_rule(pattern=["xxd"], decision="forbidden")
+prefix_rule(pattern=["hexdump"], decision="forbidden")
+prefix_rule(pattern=["od"], decision="forbidden")
+prefix_rule(pattern=["tac"], decision="forbidden")
+prefix_rule(pattern=["nl"], decision="forbidden")
+prefix_rule(pattern=["jj", "file", "show"], decision="forbidden")
+prefix_rule(pattern=["jj", "diff"], decision="forbidden")
+prefix_rule(pattern=["git", "show"], decision="forbidden")
+prefix_rule(pattern=["git", "diff"], decision="forbidden")
+prefix_rule(pattern=["git", "grep"], decision="forbidden")
+prefix_rule(pattern=["git", "cat-file"], decision="forbidden")
+prefix_rule(pattern=["git", "blame"], decision="forbidden")
+prefix_rule(pattern=["git", "log"], decision="forbidden")
+
+# prefix_rule(pattern=["python3"], decision="forbidden")
+# prefix_rule(pattern=["python"], decision="forbidden")
+# prefix_rule(pattern=["node"], decision="forbidden")
+# prefix_rule(pattern=["perl"], decision="forbidden")
+# prefix_rule(pattern=["ruby"], decision="forbidden")