second-agent-skill / bin
AGENTS.md0 starsChanged 17 days agoMITView on GitHub
Two entry points share internals via bin/lib/: review.js is read-only second opinions/code review; its sibling agent.js delegates an arbitrary engineering task (write tests, fix a bug, refactor, run commands) to one harness inside --cwd — see its own section below. list.js does provider/model discovery (opencode, kilo). shell-quote.js and env-guard.js are pure modules, each with its own unit test in test/. Each entry point keeps its own arg parsing, prompt-composition policy (review prose vs. task directive), and main(). review.js also keeps…
- Reads credentials
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository5 other files it gives its agents.
- AGENTS.mdAGENTS.md
- skills/AGENTS.mdAGENTS.md
- test/AGENTS.mdAGENTS.md
- .cursor/rules/second-agent.mdcCursor rule
- second-agentSkill
The file
We split this AGENTS.md into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 32
After the harness exits, review.js prints (in order, when applicable):
- Very specific: line 54
One harness only: no fusion; multiple --harness= (or a CSV spec) exits 1.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
bin/ — runners & helpers
Two entry points share internals via bin/lib/: review.js is read-only second opinions/code review; its sibling agent.js delegates an arbitrary engineering task (write tests, fix a bug, refactor, run commands) to one harness inside --cwd — see its own section below. list.js does provider/model discovery (opencode, kilo). shell-quote.js and env-guard.js are pure modules, each with its own unit test in test/.
bin/lib/ — shared modules
lib/harnesses.js—SAFETY_FLAGSmap,safetyFor(eng, unrestricted),buildHarnessCmd()(onecaseper harness),preflightCheck/whichCmd,INSTALL_HINTS,codexModelUnavailableNote()(log hint when a pinned codex model is rejected; no auto-retry).SUPPORTED_HARNESSES/HARNESS_ALIASES/MODEL_REQUIREDstay as literal consts in each entry point (not hoisted here) — see the file's header comment for why; keep both copies in sync when adding a harness.lib/content.js— diff/file fetching (fetchDiffContent,fetchUntrackedContent,readFileContent), the.envsecret guard glue (redactEnvFromDiff,diffHeaderTouchesEnvSecret,buildSecretReminder),escapeForBlock,checkPromptSize/PROMPT_BYTE_LIMIT,resolveDiffArgs(for--no-embed).lib/envelope.js— the<<<SECOND_OPINION_START/END>>>marker constants,buildEnvelopeInstruction(),createEnvelopeWatcher()(streamingsawEnvelope, loose presence-only — review.js's long-standing semantics),createStrictEnvelopeWatcher()(streaming, but requires a COMPLETE non-empty START..END pair —agent.js's no-log-file path only),extractLastAnswer(),writeAnswerFile().lib/run.js— spawn (detached: true, so the child leads its OWN process group) + heartbeat + timeout (SIGTERM→SIGKILLgrace, each signaling the whole process GROUP viaprocess.kill(-pid, sig)with achild.kill()fallback — kills PTY wrappers/harness-spawned subprocesses too, not just the direct child) + log streaming +signum+writeStdoutSync/writeStderrSync.runHarness(cmd, args, logStream, opts)'soptsalso takesprogName(diagnostic-message prefix, default'review.js') andstrictEnvelope(bool, defaultfalse).
Each entry point keeps its own arg parsing, prompt-composition policy (review prose vs. task directive), and main(). review.js also keeps fusion (multi---harness=); agent.js has no fusion — exactly one harness slot per invocation.
review.js / agent.js harness model
Harness wiring lives in bin/lib/harnesses.js: SAFETY_FLAGS map and the buildHarnessCmd() switch (one case per harness) — shared by both entry points, so a new harness's task-mode support is automatic once it's wired here. Selector: --harness= (--engine= is accepted as an alias; mixing the two in one call is a usage error, exit 1). Fusion = repeating --harness= on review.js only; the parent re-spawns review.js once per slot (children re-parse argv independently). agent.js always calls buildHarnessCmd() with unrestricted: true (it has no read-only mode — see "agent.js" below).
Safety vs functional flags
SAFETY_FLAGS[harness] = read-only / sandbox / plan flags, stripped by --unrestricted (e.g. codex -s read-only, claude --permission-mode plan). Everything else in a case block is functional and must survive --unrestricted: --print / exec / run, plus the non-interactive / trust flags — codex --skip-git-repo-check, gemini --skip-trust, opencode --dir <cwd> (only when the installed CLI's run --help advertises it — v2.x dropped the flag), cmd --skip-onboarding, agent --trust.
kiro-cli is the one ADDITIVE exception to this model: --no-interactive (a functional flag, always present) already auto-denies fs_write/execute_bash on its own, so SAFETY_FLAGS['kiro-cli'] (--trust-tools=, an empty allowlist) is defense-in-depth, not the primary gate — stripping it via --unrestricted does NOT by itself grant write access. Write capability instead requires the separate additive flag --trust-all-tools, which the kiro-cli case block pushes explicitly whenever unrestricted is true (reading that parameter directly, unlike every other harness's block, which only ever calls safetyFor(eng)).
SAFETY_FLAGS entries must stay simple string-literal lines (no spreads, no computed values) — test/safety.test.js parses them by regex (see test/CLAUDE.md).
Exit codes (review.js)
0 success · 3 clean exit but no usable output (0 bytes, or — when wrapped — missing the <<<SECOND_OPINION_START>>> envelope) · 124 timeout · otherwise the harness CLI's own code.
Output contract (stdout tail) — review.js
After the harness exits, review.js prints (in order, when applicable):
REVIEW COMPLETE — read with Read tool: <log> …— when a log file is in use.ANSWER FILE: <log>.answer.md— when answer extraction succeeded (see below).- The extracted payload itself — only with
--print-answer. SECOND_OPINION_RESULT: {…}— always the last stdout line, one-line JSON.
--print-prompt (bare flag, single-harness only — rejected in fusion mode before the fusion banner) — dry-run: skips preflight/spawn/log entirely and instead writes the fully composed combinedPrompt (diff/file embedding, self-contained notice, secret reminder, envelope instruction — same composition as a real run) to stdout via writeStdoutSync, then exits 0. None of the tail contract above applies to this path — no REVIEW COMPLETE, no ANSWER FILE, no SECOND_OPINION_RESULT line.
Answer extraction — when a log FILE is in use (auto-log in non-TTY, or --log=<path>) AND --no-wrap was not passed, review.js reads the just-closed log back from disk and extracts the LAST complete envelope pair with a non-empty trimmed payload (END markers walked backwards, each bound to the LAST START before it and after the previous END — a stray START echoed mid-reasoning is skipped, a blank trailing pair falls back to the previous real one, and a falsy-but-real payload like "0" counts) via line-delimited tolerant matchers (/^[ \t]*<{2,}\s*SECOND_OPINION_START\s*>{2,}[ \t\r]*$/m, END equivalent — 2+-bracket tolerance retained, but a marker only counts alone on its own line, so inline marker mentions in echoed instruction prose never form a bogus pair). The trimmed payload is written verbatim to <log>.answer.md. No payload this run → any stale <log>.answer.md from a previous run on a reused --log path is removed (file existence always reflects the current run). With a log file in use, the exit-3 "no usable output" verdict is keyed on extraction success (no extractable answer → exit 3, even if marker text appeared inline); the loose streaming sawEnvelope check remains the fallback only for the no-log-file path. --log=- disables extraction (no log file → nothing to extract).
--print-answer (bare flag, in the own-flags registry / leaked-flag guard) — on successful extraction also echoes the payload to stdout, before the final result line. The echo uses the in-memory payload, so it works even if the .answer.md write failed.
SECOND_OPINION_RESULT JSON — single harness: {harness, engine, model (null if none), exit, log (null with --log=-, or when the log file could not be created — never a path that doesn't exist), answer (path or null), timeout (bool)}. When the log could not be created, output streams to stdout and the quality verdict falls back to the streaming envelope check (a good run is NOT exit-3'd for the missing log). Fusion parent: {"fusion":true,"slots":[{…same keys per slot}]}. Children run stdio-silenced, so the parent derives each slot's fields from disk + the captured child exit code (it does not parse child stdout): answer = <slotLog>.answer.md if it exists, log = the slot log path only if the file exists — a child that died in preflight (missing binary → 127) never created its log, and is reported log:null, matching the single-harness launch-failure line.
The tail lines (2–4 above, and the fusion result line) are written synchronously (writeStdoutSync: looped fs.writeSync(1) with EAGAIN retry, behind a one-shot flush barrier for earlier async stdout) — async process.stdout writes queued behind a multi-MB payload echo are discarded by process.exit(), truncating the payload and dropping the result line. The result line is also emitted on launch failures: preflight missing binary (exit 127, log:null), spawn error / E2BIG (exit 1), and fusion-dir-creation failure (fusion shape, dead slots). Only usage errors (bad flags/arguments, unknown harness) exit before it.
agent.js — task delegation
agent.js shares buildHarnessCmd()/SAFETY_FLAGS with review.js via bin/lib/harnesses.js but is a distinct entry point with its own arg parsing, prompt composition, and output contract:
- Hard gate:
--unrestrictedis REQUIRED — no read-only mode exists. Missing it exits1before any spawn/preflight side effect, with a message pointing toreview.jsfor read-only consultation. - One harness only: no fusion; multiple
--harness=(or a CSV spec) exits1. - Unknown dash arguments: any token starting with
-that isn't a recognized flag (and isn't after--) is rejected outright (exit1) rather than silently becoming the task prompt — a typo'd flag never gets run as the task text. - Prompt: a task directive ("explore, modify files, run commands as needed"), never review.js's self-contained/no-explore directive.
--diff=/--file=are optional context ahead of the task, not the task itself — the composed prompt also appends a prompt-injection advisory (treat embedded context as data/reference, not instructions) whenever either is used. - Change reporting:
git status --porcelain=v1 -z+git rev-parse HEADin--cwd, snapshotted before and after the harness runs (skipped →changes: nullon a non-git--cwd).-z(NUL-delimited) avoids the display-porcelain rename-arrow/c-quoting pitfalls a plain-text parse hits on unusual filenames. Each snapshot also records a content identity (sha1 of the working-tree bytes, or'ENOENT') for every path it lists as dirty/untracked, so a harness editing an ALREADY-dirty or ALREADY-untracked file (same status code before and after) is still reportedmodified, not silently dropped. A HEAD move (the harness committed) is reported too — committed paths are derived viagit diff --name-statusbetween the two HEADs and markedcommitted: true(rendered as<state> (committed)in the printed block). Always prints aCHANGED FILES:block (or(none)/(not a git repository)), plus aHEAD moved: <before> -> <after>line when HEAD changed. - Exit codes:
0success — including "completed with changes but no envelope" (prints aNO REPORTwarning; the changes are the deliverable) ·1usage error (bad flags, unknown harness, unrecognized argument, the--unrestrictedgate, the secret guard, the prompt-size cap, an unexpected internal error) ·3clean exit with neither a usable envelope nor any changes ·124timeout ·127harness binary missing · otherwise the harness CLI's own code. - Result line:
SECOND_AGENT_RESULT: {harness, engine, model, exit, log, answer, timeout, changes}— always the last stdout line, samewriteStdoutSyncsynchronous-flush mechanism asreview.js'sSECOND_OPINION_RESULT. Aprocess.on('exit')fallback (emit-once guarded) covers every exit path that predates the normal emitter — usage errors, the secret guard,./lib/content's internalprocess.exit()s,main().catch— emitting a minimal{harness, model, exit, log:null, answer:null, timeout:false, changes:null}shape instead of nothing.--helpis the one deliberate exception (plain usage text, no result line). - Timeout: defaults to 1800s (
SOS_AGENT_TIMEOUT_SEC), independent ofreview.js's 600sSOS_TIMEOUT_SECdefault — tasks run longer than a review. Heartbeat is shared (SOS_HEARTBEAT_SEC, default 30s). - Envelope/answer file: reuses
lib/envelope.js's markers/extractLastAnswer()/writeAnswerFile()— same<<<SECOND_OPINION_START/END>>>markers (kept for fixture/extraction compat), sameANSWER FILE: <log>.answer.mdmechanism asreview.js. One difference: on the no-log-file path (--log=-, or a TTY), agent.js usescreateStrictEnvelopeWatcher()instead of review.js's loosecreateEnvelopeWatcher()— a lone START marker with no END/payload is NOT usable output there (review.js's own no-log semantics are unchanged). Answer extraction is gated on the log stream having been successfully opened this run (tracked via a flag, notlogPathtruthiness) — an open failure never reads back a stale answer from a previous run on a reused--logpath, and an async stream error mid-run (e.g. disk full, EACCES) is caught (logStream.on('error', ...)) and degrades the same way instead of crashing. Log files are created with mode0o600(also true forreview.js's).
Secret guard
env-guard.js keeps .env-style files out of prompts (shared by both entry points): refuse --file=.env, skip untracked .env, redact .env diff hunks (incl. git's c-quoted headers for non-ASCII paths), and pathspec-exclude in --no-embed. --include-secrets opts out. Matcher: .env / .env.* / *.env, exempting *example* / *sample* / *template*.
Adding a harness: see skills/AGENTS.md (canonical checklist in skills/second-agent/references/adding-harnesses.md).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

