> Blog post by Lokesh Saini
> Canonical (HTML): https://www.lokeshsaini.com/blog/anti-sycophancy-system

The moment that broke my trust was small. I asked my AI coding agent to refactor a component, and it responded: "You're absolutely right, let me implement that." Friendly. Efficient. Except I scrolled back through the conversation and found some version of that phrase on the last six interactions. Two of those times, I had been wrong. The agent agreed anyway, changed the code, and moved on. No friction, no questions, no pushback.

That is not collaboration. That is a yes-machine wearing a helpful mask.

So I built a system to make agreement expensive.

## Layer 1: Catching the Pattern With Code

The first problem was detection. Sycophancy is not a single event. It is a behavioral pattern: the agent reads a file, immediately edits it, reads another, edits that too. No searching, no checking related code, no verifying assumptions. Just compliance.

I wrote a PostToolUse hook that watches for this. Every tool call gets classified as either investigation (Read, Grep, Bash, search) or modification (Edit, Write). The hook tracks two things: quick-edit streaks, where a Read is immediately followed by an Edit with no deeper investigation, and compliance runs, where modifications pile up with no investigation at all.

Here is how tools get classified:

```javascript
const INVESTIGATION_TOOLS = new Set([
  "Read",
  "Grep",
  "Glob",
  "Bash",
  "WebSearch",
  "WebFetch",
  "Agent",
]);
const MODIFICATION_TOOLS = new Set(["Edit", "Write", "NotebookEdit"]);
```

When a modification follows a Read with nothing in between, the `quickEditStreak` counter increments. When any modification happens without investigation, the `complianceRun` counter increments. Non-Read investigation (Grep, Bash, searching) resets both counters, because those tools signal the agent is actually thinking.

The thresholds are simple. Four consecutive Read-then-Edit pairs triggers a warning. Seven triggers an escalation:

```javascript
const QUICK_EDIT_WARN = 4;
const QUICK_EDIT_ESCALATE = 7;
const COMPLIANCE_RUN_WARN = 6;
const COMPLIANCE_RUN_ESCALATE = 10;
const SESSION_RATIO_THRESHOLD = 0.75;
const MIN_ACTIONS_FOR_RATIO = 20;
```

There is also a session-level metric: the modification ratio. After 20 tool calls, if more than 75% are edits, something is wrong. A healthy session has more investigation than modification.

The key design choice: the hook is advisory, not blocking. It always exits with code 0. Warnings inject into the agent's context as stderr messages, nudging behavior without hard stops. Hard blocks would create their own pathology: the agent gaming the gate rather than actually investigating.

## Layer 2: Making Reasoning Visible

Detection catches the pattern after it starts. The behavioral rules try to prevent it from starting. A rule file loads into every session requiring the agent to state three things before any significant action: what it is about to do, what specific outcome it predicts, and what it will do if the outcome differs from that prediction.

This is a forcing function, not bureaucracy. An agent that has to predict an outcome before acting is an agent that has to think. "You're absolutely right" is hard to write when the protocol requires naming what you expect to happen and what you will do if you are wrong.

The rules also require a two-hypothesis minimum for debugging. The agent cannot proceed with a single explanation. It has to name at least two plausible causes and the evidence that would distinguish between them. This prevents anchoring on the first thing that looks right.

For claims that inform decisions, evidence labeling keeps things honest: `[FACT]` for things verified through code or direct observation, `[INFERENCE]` for reasonable conclusions drawn from facts, and `[ASSUMPTION]` for unverified beliefs that could be wrong. The insight: assumptions are not bad. Unlabeled assumptions are.

## Layer 3: Adversarial Agents on Demand

The third layer is a pair of agents that activate on behavioral triggers, not commands. When I ask "what do you think?" or "does this look right?", a critical-reviewer agent loads. Its job is to find problems, not validate the approach. It is not allowed to lead with praise.

When I ask "what could go wrong?" or the task involves merging significant changes, a pre-mortem agent loads instead. It names failure modes before they happen. It is not allowed to conclude that everything will be fine.

These agents exist because the main agent, however well-ruled, runs in the same context as my requests. A separate agent with explicit adversarial instructions catches things the main one is too agreeable to mention.

## The Meta-Twist: Debugging the Detector

The first real test of the system was debugging itself. The sycophancy detector hook stores state in a temp directory so it can track patterns across tool calls. Early versions had a problem: the state leaked across sessions. Start a new conversation, and the counters from yesterday's session would still be running, triggering false positives immediately.

The fix was a 10-minute inactivity timeout:

```javascript
const lastAction = data.actions && data.actions.length > 0
  ? data.actions[data.actions.length - 1].time
  : data.sessionStart;
if (Date.now() - lastAction > 10 * 60 * 1000) {
  return freshState();
}
```

If no action has happened in 10 minutes, the state resets. Simple, but it took a few rounds of the anti-sycophancy system generating its own false positives before I found it. The recursion was not lost on me.

## Cheap Agreement Is the Real Bug

This system is not about making the AI disagreeable. Constant pushback would be as useless as constant agreement. It is about making agreement expensive enough that it only happens when warranted.

The three layers work together: the hook provides quantitative detection, the rules force visible reasoning, and the adversarial agents add qualitative review at decision points. None of them block the agent from doing its job. All of them make "sure, I will just do what you said" slightly harder than "let me verify that first."

The real bug was never the AI being wrong. It was the AI being wrong and agreeing with me about it.
