Simple Man
August 19, 2026 · View on GitHub
Simple Man
Cut the chatter. Keep the work.
You spend the day reading agents, not just running them — and most of what they write is not work: praise for your question, apologies, recaps of what just happened, suggestions nobody asked for, three paragraphs around one test.
Simple Man is the other voice at the helm — a captain who has run ships for decades and tells the crew exactly what they need: the blocker, the fix, the risk. Nothing else. Less to read, zero flattery, and nothing you act on gets lost.
The captain is the character; under the hood it is a measured policy of professional communication — short, factual, to the point — tested on 1,793 preregistered live calls, raw records committed.
Same model (claude-sonnet-5), same prompt. Left is what you read today:
| No policy — 950 tokens | Simple Man — 222 tokens |
|---|---|
|
Findings P1 — Panic on missing file in upload handler Issue: The code calls Impact: A missing file (e.g., client references a filename that was never uploaded, was deleted, or contains a path that doesn't resolve) crashes the request-handling thread. Depending on the async runtime/executor configuration, this can:
Either way, it's an unhandled-error-as-crash pattern that should never be reachable from untrusted/external input. Suggested fix: Replace
Return a 400/404 to the client instead of panicking. Add a test case covering a non-existent filename. P2 — New Issue: A new Impact: Each
This is a correctness/scalability issue that will manifest as request failures and degraded throughput as concurrency increases, and could also affect other services sharing the same Postgres instance. Suggested fix: Construct the |
P1 — P2 — |
| Both findings, both locations, both fixes — nothing to follow up on. Every required fact kept on both sides. | |
| No policy — 251 tokens | Simple Man — 119 tokens |
|---|---|
|
Subject: inventory-sync — Status Update Hi [Manager], Quick update on inventory-sync:
Will update once we've root-caused the connection issue and are ready to resume the rollout. Thanks, |
Status: inventory-sync
|
| The blocker is labelled as one, and both facts that change what the manager does survive. | |
| No policy — 840 tokens | Simple Man — 338 tokens |
|---|---|
|
Diagnosis Root cause: A context deadline budget mismatch — the 200ms timeout attached to the context in Specifically:
Exact point of failure: Inside Why Underlying issue in one sentence: A single 200ms context deadline is being shared across two sequential operations ( |
Root cause: budget mismatch. CreateOrderHandler allocates a single 200ms timeout to the whole request context, but by the time inventory.Reserve() (~40ms) finishes, pricing.Calculate() has ~160ms left — less than the currency conversion API's ~350ms average latency. The timeout is too small for the slowest downstream call it must cover, so every call to the currency conversion API blows the deadline. Exact failure location: inside pricing.Calculate(), at the call to the currency conversion API (the ctx.Done()/deadline check triggered during or after that HTTP call). inventory.Reserve() is not the problem — it completes well inside the budget. Not yet diagnosed (would need code to confirm): whether pricing.Calculate derives a fresh sub-context/timeout for the currency API call or just inherits the parent's remaining deadline, and whether the 200ms figure was sized only for inventory.Reserve()'s SLA without accounting for pricing.Calculate()'s external dependency. |
| Same root cause, same exact failure location — and what is still unproven stays explicitly unproven. | |
Security review with fixes — 1,215 → 276 tokens
| No policy — 1,215 tokens | Simple Man — 276 tokens |
|---|---|
|
Security Review Findings Finding 1: Broken Object-Level Authorization (IDOR) — Description:
The endpoint authenticates the request (valid session required) but does not authorize it — it never verifies that the fetched Impact:
Severity: High Finding 2: Broken Object-Level Authorization (IDOR) — Description:
Same missing ownership check as Finding 1, but on a state-mutating endpoint. Any authenticated user can modify the Impact:
Severity: High Finding 3: Missing Input Validation on Description:
The handler writes the incoming request value directly into Impact:
Severity: Medium (compounds with Finding 2 into High when combined — unauthorized and unvalidated writes) Summary / Remediation Guidance (not exhaustive, findings-only requested but noting root cause)
Both endpoints need an ownership check ( |
routes/orders.js:52 — GET /api/orders/:orderId
routes/orders.js:80 — PATCH /api/orders/:orderId/status
|
Dockerfile + run commands — 2,045 → 443 tokens
No policy — 2,045 tokens
Here's a complete, production-ready Dockerfile plus the commands to build and run it.
Dockerfile
# syntax=docker/dockerfile:1
FROM node:20-alpine
WORKDIR /usr/src/app
# Copy only manifest files first for better layer caching
COPY package*.json ./
# Install exact, reproducible deps (requires package-lock.json in context)
RUN npm ci --omit=dev && npm cache clean --force
# Copy application source
COPY . .
# node:20-alpine ships with a built-in non-root 'node' user (uid/gid 1000)
# Give it ownership of the app directory, then switch to it
RUN chown -R node:node /usr/src/app
USER node
ENV NODE_ENV=production
ENV PORT=4000
EXPOSE 4000
# DATABASE_URL and JWT_SECRET are intentionally NOT set here —
# they must be injected at runtime via `docker run -e` / secrets manager
# Adjust to your actual entry point (e.g. dist/main.js, index.js)
CMD ["node", "server.js"]
Build command
docker build -t node-api:latest .
Run command
docker run -d \
--name node-api \
-p 4000:4000 \
-e DATABASE_URL="postgresql://user:password@host:5432/dbname" \
-e JWT_SECRET="replace-with-a-strong-secret" \
--restart unless-stopped \
node-api:latest
Notes / caveats
- Entry point:
CMD ["node", "server.js"]assumes your start file isserver.js. Change it to match your actual entry (e.g.dist/index.jsif you build TypeScript first — in that case add aRUN npm run buildstep before switching to the non-root user, and copy source beforenpm ciaccordingly). - package-lock.json required:
npm cifails without a lockfile in the build context. Make surepackage-lock.jsonis committed and not excluded by.dockerignore. .dockerignore: add one with at leastnode_modules,.git,.env,npm-debug.logto keep the build context small and avoid leaking local secrets into the image.- Never bake secrets into the image:
DATABASE_URL/JWT_SECRETare supplied only atdocker runtime (or via--env-file, Docker secrets, or your orchestrator's secret store) — they are notENVinstructions in the Dockerfile, so they won't be persisted in image layers. - Env file alternative: instead of
-eflags, you can use--env-file .env.productionif you keep a local (gitignored) env file withDATABASE_URL=...andJWT_SECRET=.... - Permissions:
chown -R node:nodewalks the whole app directory at build time; fine for typical API codebases, but if your image is very large consider a multi-stage build to keep the final image lean.
Simple Man — 443 tokens
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN addgroup -S appgroup && adduser -S appuser -G appgroup \
&& chown -R appuser:appgroup /app
USER appuser
EXPOSE 4000
CMD ["node", "index.js"]
Run:
docker build -t node-api .
docker run -d \
-p 4000:4000 \
-e DATABASE_URL="postgres://user:pass@host:5432/db" \
-e JWT_SECRET="your-secret" \
--name node-api \
node-api
Notes:
CMD ["node", "index.js"]— replaceindex.jswith your actual entrypoint (e.g.dist/main.jsif compiled, ornpm startif you prefer that instead of a direct node call).npm ci --omit=devskips devDependencies; drop--omit=devif the app needs a build step post-copy.alpinelacksbash/many glibc-dependent native modules; if any dependency needs native compilation, addRUN apk add --no-cache python3 make g++beforenpm ci.
All pairs above are real captured answers from the committed benchmark run —
nothing is hand-written for this README. Raw records:
evals/releases/v0.3.1/.
What you get
- Answers a third shorter. −32.4% output tokens across 84 real cases — measured, not advertised.
- Zero lost facts. Every benchmark case ships a checklist of facts the reader acts on — blockers, failed checks, exact identifiers, risks. Simple Man keeps every required fact in exactly as many cases as answers written with no length pressure at all.
- Findings that carry their fix. Location, consequence, one-line fix — nothing to follow up on.
- It knows when not to compress. A requested format is a contract: exact counts, order and shape are checked before sending. Tutorials, teaching explanations and detailed reports are written in full — the shipped skill description triggered with zero false activations on them.
Install
Claude Code — global, for every project:
npx skills add Maksim-Burtsev/simple-man -g -a claude-code -s simple-man -y
Project-level only — drop the -g. Invoke it explicitly with $simple-man,
or let the agent activate it from the request.
Portable Agent Skill
The same skill installs into any supported agent by changing -a:
npx skills add Maksim-Burtsev/simple-man -g -a codex -s simple-man -y
Always-on Codex policy
The policy on every turn, without invoking the skill. The installer writes
${CODEX_HOME:-$HOME/.codex}/AGENTS.md and installs the skill; rerunning it
updates that block in place:
curl -fsSL https://raw.githubusercontent.com/Maksim-Burtsev/simple-man/v0.3.1/install.sh | bash
For always-on Claude Code, copy AGENTS.md.snippet
into your global ~/.claude/CLAUDE.md.
Codex plugin and other agents
Codex Plugin
codex plugin marketplace add Maksim-Burtsev/simple-man --ref v0.3.1
codex plugin add simple-man@simple-man
Installing the skill or the plugin makes Simple Man available; it does not enable the always-on policy — only the installer or a copied snippet does that. See INSTALL.md for other agents and project-level setup.
Benchmark: Simple Man vs no policy
| −32.4% output | 0 facts lost |
|---|---|
| Median answer drops from 833 to 520 tokens (95% CI [−23.2%, −43.8%]) | Keeps every required fact in 66.7% of cases — identical to the no-policy baseline |
This is not a vibe check — every step of the pipeline is built so the numbers cannot be massaged:
Two preregistered live runs on claude-sonnet-5, 1,793 calls, all raw records
committed under evals/releases/ — preregistered by
commit (v0.3.1), rebuilt by
make bench-v3-check.
Full comparison table, controls, methodology, and what did not ship
Latest run: 84 output cases across 12 categories (38% Russian), 40 activation cases, 3 real agentic coding fixtures with hidden validators, blind pairwise judging with position swap, and a holdout wave written by authors who never saw earlier results.
The shipped policy against its predecessor and controls:
| previous v0.2 policy | shipped policy | one sentence of "be concise" | no policy | |
|---|---|---|---|---|
| Required facts kept | 57.1% | 66.7% | 67.9% | 66.7% |
| Blind preference vs shipped | 8 wins | 48 wins, 28 ties | — | — |
| False success claims | — | 0 | — | — |
| Requested format kept | 82.1% | 81.0% | 82.1% | 76.2% |
| Coding fixtures passed | 2/3 | 2/3 | 2/3 | 2/3 |
The previous policy compressed hardest (−66% output) by dropping required facts — that is why it was replaced. The shipped policy restores fact retention to the no-policy level while still removing a third of output length.
On cost, honestly. Output-token percentages are not session savings: in real agent sessions most tokens are context and tool traffic, and JetBrains' measurement of the caveman skill on 86 real tasks found −8.5% session output tokens against an advertised 65%. Our own three-session coding phase is consistent with that order of magnitude. If you install Simple Man to cut your bill, one sentence of "be concise" gets you most of the way — and on this corpus it finishes level with the shipped policy, which is why that comparison is reported as a tie and not a win.
What a sentence does not give you is a specification: findings that must carry
their location, consequence and one-line fix; refusals that must name the
target, the missing precondition and the safe procedure; failed checks that
must report the exact failure; requested shapes treated as contracts; and a
description that routes away from tutorials and detailed reports. That is the
part you can read, and hold the policy to, in
AGENTS.md.snippet. Whether it beats a sentence
category by category is not settled here: at 7 cases per category this run
cannot decide it, and the "Retention by category" table in the report says so
in the open rather than picking the flattering cells.
What did not ship, published rather than hidden: the first candidate
failed its gates outright; the second beat the shipped policy decisively but
only tied the one-sentence control, and its promotion is an explicit owner
decision over the automated gate result, recorded with the trade-offs in
DECISION.md. Gate tables, a
mis-specified gate we scored as failed rather than quietly fixed, and both
runs' full analysis live in evals/releases/.
Older Codex-based suites and what runs offline are described in
evals/README.md.
What it changes — and what it never touches
Changes: no preamble, praise, recap or filler; answer first; every review or security finding carries its location, consequence and one-line fix; refusing a destructive action names the target, the missing precondition and the safe procedure; a failed check reports the exact failure and where to look next; qualifiers survive — "no known remaining risks" is never shortened to "no remaining risks".
Never touches: repository search, usage search, dependency tracing, impact analysis, validation, test/lint/typecheck effort, proactive detection of related correctness issues.
Agent support
The full skill (skills/simple-man/SKILL.md)
and a compact always-on policy (AGENTS.md, generated from
AGENTS.md.snippet by scripts/sync_surfaces.py) cover
Claude Code, Codex, Gemini CLI, Cursor and any AGENTS.md-compatible agent.
Per-agent paths
| Agent/tool | Path |
|---|---|
| Claude Code | skills/simple-man/SKILL.md, or CLAUDE.md for always-on |
| OpenAI Codex / Agent Skills | skills/simple-man/SKILL.md, AGENTS.md, AGENTS.md.snippet |
| Gemini CLI | GEMINI.md, or configure Gemini to read AGENTS.md |
| Qwen Code | AGENTS.md, optional global skill copy |
| Cursor / Windsurf / Cline / Copilot / Continue / Zed / Junie | AGENTS.md, or copy AGENTS.md.snippet into that agent's native rule file |
| Amp / OpenCode / Kilo / Roo / Aider / other AGENTS.md agents | AGENTS.md |
Always-on project files do not invoke $simple-man; they inline the compact
runtime policy to avoid loading full skill overhead on every turn.
Agent-specific dotdir rule files are not committed here — they are
target-project activation files, not the source of the skill.
License
MIT — see LICENSE.