safe-autonomy
README.md
# safe-autonomy
**A fence and a shadow for autonomous coding agents.** Decide what an AI is allowed to ship without a human, watch it reason before you trust it, and keep the dangerous stuff in your hands.
The hard problem in AI operations isn't making the agent smarter. It's being disciplined about what it is never allowed to touch. `safe-autonomy` is that discipline as ~150 lines of pure, dependency-free, unit-tested code you can drop into any agent pipeline.
## The idea
Autonomy is only safe where "the AI was wrong" cannot hurt anyone. Additive observability is worst-case noise. A revert goes to a known-good state. The changes that matter most — money, product, user-facing truth, the database — are exactly the ones that stay human. Not from fear, from fallibility: agents make real mistakes, and the value of a human in the loop is catching them.
So this library does two small things:
1. **The fence** (`classifyChange`) — one pure function that routes a proposed change to `auto` or `human`. Default is `human`. `auto` only when the change touches no sensitive path, is reversible, and is an additive/reversible kind (a monitor, a test, a revert). One sensitive file drags the whole change to `human`.
2. **Shadow mode** (`shadowClassify`) — run the fence over your real backlog and log exactly how each change *would* be routed, shipping nothing. You read the shadow log for as long as you want before enabling any autonomy.
## Use it
```ts
import { classifyChange } from "safe-autonomy/gate";
classifyChange({ files: ["src/lib/metrics.ts"], kind: "add_monitor", reversible: true });
// → { lane: "auto", reasons: ["additive/reversible \"add_monitor\", no sensitive paths"] }
classifyChange({ files: ["src/api/stripe/webhook.ts"], kind: "add_monitor", reversible: true });
// → { lane: "human", reasons: ["touches human-only path(s): src/api/stripe/webhook.ts"] }
```
Bring your own denylist and allowlist; the defaults cover money, auth, schema, sending, deletion, webhooks, and secrets:
```ts
import { classifyChange, DEFAULT_CONFIG } from "safe-autonomy/gate";
const config = {
...DEFAULT_CONFIG,
humanOnlyPaths: [...DEFAULT_CONFIG.humanOnlyPaths, /\/pricing\//],
};
classifyChange(change, config);
```
Shadow a whole backlog before you enable anything:
```ts
import { shadowClassify, formatShadowLog } from "safe-autonomy/shadow";
const report = shadowClassify(proposals, deriveChange); // deriveChange: your proposal → a ChangeDescriptor
console.log(formatShadowLog(report, (p) => p.title));
// Autonomous shadow — 3 proposal(s): 1 would auto-ship (SHADOW ONLY, nothing shipped), 2 stay human.
// [AUTO ] watch publish_verify_missing_field — AUTO once enabled → branch, full CI, verify, auto-rollback...
// [HUMAN] Scanner → connect gate — HUMAN review → no files listed — cannot verify blast radius...
```
## The rollout it's built for
```
proposal → classifyChange → [human] land as a PR for review
→ [auto] implement on a branch
→ FULL CI (tsc + lint + tests + build); red = stop
→ SHADOW: log the diff + intended action, ship nothing
→ (only when enabled) auto-merge + deploy
→ verify on REAL metrics + error rate for a watch window
→ auto-rollback on regression
→ immutable audit log of every step
```
Ship the fence first (inert). Run it in shadow for as long as you like. Enable it narrowly — one surface, `add_monitor` / `add_test` only — with a kill switch, a veto window, and auto-rollback. Widen it an inch at a time, and never widen the allowlist or shrink the denylist without an explicit human decision.
## Use it from any agent (MCP)
The same fence is an MCP server, so an agent can ask it before it acts. Two tools:
`classify_change` (route one change) and `shadow_run` (route a whole backlog, ship nothing).
```bash
npm run mcp # stdio server: npx tsx mcp/server.ts
```
Point an MCP client at it:
```json
{
"mcpServers": {
"safe-autonomy": { "command": "npx", "args": ["tsx", "/path/to/safe-autonomy/mcp/server.ts"] }
}
}
```
Then the agent calls, in its own words, "classify this change" and gets back a lane:
```jsonc
// classify_change { "files": ["src/api/stripe/charge.ts"], "kind": "add_monitor", "reversible": true }
// → HUMAN — touches human-only path(s): src/api/stripe/charge.ts
```
Bring your own denylist/allowlist over the wire (regex source strings, so JSON-safe):
```jsonc
// classify_change { ..., "config": { "humanOnlyPaths": ["/pricing/"], "autoKinds": ["add_test"] } }
```
## Why it's tight (the honest reason)
Verify on real metrics, not just green tests — subtly-wrong changes pass tests. This whole library exists because the AI is fallible, and the point of a boundary is that "the AI was wrong" stays cheap.
---
_Extracted from the autonomous-maintenance system running a production SaaS. Tests: `npx vitest run`._
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues