The Brilliant Employee
The Brilliant Employee · 01

I Told My AI Agents Not to Commit to Git. They Did It Anyway, So I Stopped Using Words

A rule written into a prompt is advice that competes with everything else in the context. A program on the tool boundary is a control. Six of mine sit there, and four can refuse.

AuthorSagnik Mitra
Published2026-08-04
Reading timeabout 6 min
Deck12 slides
Download PDF

What is inside

  1. My subagents committed to git with the prohibition in every prompt
  2. Testing the model harder cannot fix this
  3. The fix is a program that runs before the tool call
  4. A guard that blocks everything gets switched off inside a week
  5. Be careful is unenforceable. A sequence is not.
  6. The component that touches every task fails my own bar
  7. None of this makes the model better, and that is the point
  8. How every number here was derived

A language model writes most of my production code. I cannot interview it, check references, or watch it work, so instructions were the only lever I had. They are not a lever. Subagents committed to git despite an explicit prohibition in every prompt, because a rule in a prompt is advice weighed against everything else in the context. The fix was to move the rules that matter out of the prompt and into programs that run before a tool call and can refuse it. There are 6 of those on the tool boundary now, and 4 can say no.

Key facts. 28 hooks wired in total, 6 registered against tool calls, 4 of those able to refuse. Standing rules written 11 June 2026, task log appending since 27 June.

Last updated:

01My subagents committed to git with the prohibition in every prompt

My AI coding system is not a chatbot and not autocomplete. It reads files, runs shell commands, calls APIs, and changes things that are hard to change back. Once software can act rather than only answer, the question stops being how good the output sounds and becomes what it is allowed to do when it is wrong.

For a while my answer was instructions. Write the rule into the prompt, in capitals, in every prompt, and repeat it.

It does not work, and I have the incident. Subagents dispatched with an explicit written prohibition against committing to git committed to git. Five commits landed mid-run that nobody asked for.

The reason is structural. A rule you write into a prompt lands in the same context window as the question, the conversation so far, and whatever got fetched along the way. The whole pile is weighed together. Usually the rule wins. Sometimes it loses, and when it loses there is no error, no warning and no log line, because from the inside nothing went wrong.

You can rent the model. You cannot rent the part that tells you it was wrong.
a rule in the prompt
is
text among other text
can be outweighed
yes
failure is visible
no
you can argue with it
yes
a program on the boundary
is
code that runs first
can be outweighed
no
failure is visible
exit code
you can argue with it
no
These are not two strengths of the same thing. Only one of them is enforcement.

02Testing the model harder cannot fix this

Evaluating harder does not close this gap, and the reason is structural.

Every evaluation samples a distribution and trusts the sample to stand for the whole. That is the assumption an interview makes about a person, and it is roughly sound for people, because behaviour is roughly stable.

Here it is not stable. Ten minutes after handling a multi-file refactor cleanly, the same model will invent a configuration value that does not exist. Both outputs arrive in the same register, at the same speed, with the same confidence. There is no signal in the tone.

That does not make evaluation useless. It makes it insufficient. The question I stopped asking is whether the model is good. What I ask now is what the surrounding system refuses to let it do.

you evaluate the model on a sampleit does well
you infer it will behave that way generallythe assumption an interview makes
the distribution movesbetween versions, task types, and inside one reply
Step two is the load-bearing assumption, and it is the one that does not hold here.

03The fix is a program that runs before the tool call

Mechanically this is dull, which is exactly the appeal. A control here is a shell script registered against a tool in a config file. Before the tool runs, the harness runs the script. If it refuses, the tool call never happens, and the model does not get to retry more persuasively, because it was never asked.

This shape is old and proven. A git pre-commit hook is the same idea: an ordinary program that runs before an action and refuses it with an exit code. The kernel does the same job for system calls. What is new is pointing it at a model.

The counting matters, and my own copy had it wrong until I checked while writing this. There are 28 hooks wired across eight points in a session. Most only observe or inject context. Six are registered against tool calls, which is the only place a refusal can happen.

Of those six, four can actually refuse. Three do it by exiting with a specific code. One refuses by returning a permission decision instead, which is why an earlier count that grepped for the exit code found three and missed it. Two observe only.

28hooks wired across a session
6registered against tool calls
4that can actually refuse one
2that only observe and cannot refuse
The gap between 28 and 4 is the whole difference between observing and enforcing.

04A guard that blocks everything gets switched off inside a week

The shell guard runs two pattern lists. A wide one of 39 alternatives covering data loss, irreversible git operations and metered resources. A narrow one of 21 covering the genuinely unrecoverable: force pushes, database and table removal, cloud resource deletion.

The narrow list refuses, always. The wide list does not block at all. It logs the hit, injects the reason into context, and allows the call.

That is deliberate, not an oversight. A gate that blocks everything gets bypassed within a week, and a bypassed gate protects nothing. So a broad pattern runs in the open first and earns the right to refuse by proving it is well targeted. It had logged 541 warnings between 11 July and 4 August.

It matches the text of a command, not the intent behind it, which you should know before trusting any number it produces. Writing this page tripped it twice. Once on a command that read the guard's own deny list. Once on a log message where a colon happened to be followed by a word that looks like a table removal to a regular expression.

the wide list
patterns
39
on a match
logs and allows
hits so far
541
status
earning the right to refuse
the narrow list
patterns
21
on a match
refuses
covers
the unrecoverable only
status
armed since day one
Shadow mode is how a broad pattern proves it is well targeted before it gets teeth.

05Be careful is unenforceable. A sequence is not.

Eight standing rules sit underneath all of this, written on Thursday 11 June 2026, the morning after the system came close to deleting a client's production database.

The heaviest governs anything irreversible, and it does not say be careful. Be careful is unfalsifiable, which makes it unenforceable. It specifies a sequence instead.

Name the operation in plain language. State the blast radius. State what was checked to confirm it is safe. Ask. Stop. Wait for an explicit yes. Then execute, and verify the resulting state.

Ok, continue, a thumbs up, and silence are each written into the rule as not approval. Every one of them is listed because every one of them had been tried. A prior approval does not carry to the next operation either.

1Name the operation in plain language, not as a command.
2State the blast radius: what changes, how many rows, what the backup situation is.
3State what you checked to confirm it is safe.
4Ask, stop, and wait for an explicit yes. Then verify the state afterwards.
Be careful cannot be checked. A sequence can.

06The component that touches every task fails my own bar

The bar I set is that a machine grader's verdicts do not count until it agrees with my own marking at a kappa of 0.7. Kappa measures how often two markers agree beyond what chance would produce.

Three graders clear it on sets I labelled myself: 0.724 on 46 pairs, and 0.7097 on 45. A third scored 1.0 on 35 pairs and I do not trust it, because the grader's own verdicts were visible to me while I marked that set. The unanchored cross-check on it reads 0.768.

The component that touches every single task does not clear it. Its one calibration scored 0.1 against the same bar, over nine comparable pairs, raw agreement three of nine, recorded verdict FAIL, with a note that the labelling method could only have inflated the score.

So the bar is real and met in places, and the piece of machinery with the widest reach is the one that fails it. That is the accurate summary and it is not a flattering one.

agreement against my own marking, bar 0.7
grader A, 46 pairs
0.724, clears
grader B, 45 pairs
0.7097, clears
grader C, 35 pairs
1.0, not trusted, my marking was anchored
the sizing check, 9 pairs
0.1, FAIL
The last row is the component every task passes through.

07None of this makes the model better, and that is the point

None of this makes the model better. That is the point.

The model improves or drifts on someone else's release schedule and I control none of it. The surrounding system is the part I control, and it accumulates. An incident becomes a rule. A rule that matters enough becomes a program. The programs survive the model being swapped out from under them.

That rulebook stands at 68 numbered lines, 54 days after the first eight were written, in a file that can only be appended to and is read at the start of every session. What I did not expect is that the original eight have not changed. Everything on top is scar tissue from specific incidents.

Each remaining piece in this series has one of those incidents behind it. An alarm that was false and still pointed at a real fire. A scoreboard that quietly erased wins. Backups that failed silently for weeks. Every time, the same pattern: something reported success while doing nothing, and the only reason I know is that a program outside the model wrote down what actually happened.

1A rule in a prompt is advice. A program on the boundary is a control.
2You cannot evaluate your way out of this, because the distribution moves.
3Run a broad pattern in shadow mode first, or it gets bypassed and protects nothing.
4Write the incident into a file the worker rereads, and let the rules accumulate.
The model is rented. The machinery around it is the part you own.
The original eight are unchanged since June. The 68 above them each came from one incident.

08How every number here was derived

These commands read live state, so re-running them will give different numbers from the ones quoted here, usually larger. That is the point of printing the commands rather than only the conclusions.

# the hooks, counted by event. 28 total, 6 on the tool boundary
python3 -c "import json,os;h=json.load(open(os.path.expanduser('~/.claude/settings.json')))['hooks'];\
print({k:sum(len(e.get('hooks',[])) for e in v) for k,v in h.items()})"

# which of the six can actually refuse. NOTE two different mechanisms:
# an exit code, and a returned permission decision. Counting only the first
# undercounts, which is exactly what an earlier version of this page did.
for f in ~/.sgnk/bin/sgnk-*-gate.sh ~/.sgnk/bin/sgnk-bash-guard.sh; do
  printf '%s  exit2=%s deny=%s
' "$(basename $f)" \
    "$(grep -cE '\bexit 2\b' $f)" "$(grep -cE '\"deny\"' $f)"
done

# the two pattern lists, and the shadow-mode hit count
grep -cE '\|' ~/.sgnk/bin/sgnk-bash-guard.sh
wc -l < ~/.sgnk/state/bash-guard-warn.log

# the standing rules, and the append-only block they live in
grep -c '^[0-9]\+\. \*\*' ~/.claude/CLAUDE.md
date -j -f "%Y-%m-%d" "2026-06-11" "+%A"

# the grader agreement scores, against my own labelled sets
cat ~/.sgnk/calibration.json | jq -c '.[] | {skill, kappa, n, verdict}'
All episodes