From aac37993e803f8c9ad189cf67d263f8a930c83c1 Mon Sep 17 00:00:00 2001 From: Graeme Ellis Date: Thu, 30 Jul 2026 07:41:42 -0700 Subject: [PATCH 1/5] Add second-opinion skill Sends the whole session transcript, verbatim, to a different agent CLI and reports its answer back unedited. An authored briefing frames the question: it fixes which options exist and which premises are settled, so the second opinion can only choose between decisions already made. Passing the transcript hands over the frame itself, and in testing the other model challenged premises unprompted. Co-Authored-By: Claude Opus 5 --- README.md | 3 +- skills/second-opinion/SKILL.md | 47 ++++++++++++++++++++++++++++ skills/second-opinion/transcripts.md | 35 +++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 skills/second-opinion/SKILL.md create mode 100644 skills/second-opinion/transcripts.md diff --git a/README.md b/README.md index f1117cf..dd2dbdf 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,10 @@ A skill is a folder with a `SKILL.md` describing a task in enough detail that an | [`fix-pr-comments`](skills/fix-pr-comments) | Fetches review comments from a GitHub PR, triages them, and fixes each one test-first — write a failing test capturing the reviewer's concern, make it pass, then resolve the thread. Handles skip-reason replies for feedback you're not taking. | | [`write-a-pitch`](skills/write-a-pitch) | Collaborates with you on a [Shape Up](https://basecamp.com/shapeup) pitch — problem, appetite, solution, rabbit holes, no-gos. Explores your codebase so the pitch is grounded in real code, and aims for a pitch complete enough that an agent could generate a spec from it. | | [`chip-handoff`](skills/chip-handoff) | Summarizes the current conversation and hands it to a fresh session as a clickable task chip, rather than writing a handoff file you have to shuttle around yourself. | +| [`second-opinion`](skills/second-opinion) | Sends your whole session, verbatim, to a different agent CLI — Codex, Cursor, or Claude Code — and reports its answer back unedited alongside where it disagrees. The transcript goes over as-is rather than as a summary, so the other model can reject the framing rather than just pick between the options you already settled on. | | [`writing-tighten`](skills/writing-tighten) | Checks a draft against nine prose rules — kill the warm-up, cut 10%, no adverbs, active voice, concrete over abstract — one pass per rule. Reports what each pass found and applies only the fixes you pick, so the edit stays yours. Command-only; it won't fire on its own. | -`fix-pr-comments` needs the [`gh`](https://cli.github.com) CLI. `chip-handoff` is the one skill here that isn't portable — it depends on the `spawn_task` tool, which only exists in the Claude Code desktop app. +`fix-pr-comments` needs the [`gh`](https://cli.github.com) CLI. `second-opinion` needs at least one other agent CLI installed (`codex`, `cursor-agent`, or `claude`) and `jq` to extract the transcript; it sends the session to another vendor, so read the privacy note in its `SKILL.md` before using it on anything sensitive. `chip-handoff` is the one skill here that isn't portable — it depends on the `spawn_task` tool, which only exists in the Claude Code desktop app. `writing-tighten`'s rules are adapted from Kaguura Gichuru's [20,585 New Subscribers in 90 Days](https://kaguura.substack.com/p/90-days-20585-new-subscribers-heres). The essay-structure and audience-growth advice in that piece was left out — this is only the repeatable prose rules. diff --git a/skills/second-opinion/SKILL.md b/skills/second-opinion/SKILL.md new file mode 100644 index 0000000..2cd56ba --- /dev/null +++ b/skills/second-opinion/SKILL.md @@ -0,0 +1,47 @@ +--- +name: second-opinion +description: Ask a different AI and read its take. +disable-model-invocation: true +argument-hint: "[harness] [model] [effort] — e.g. codex gpt-5.6-sol high" +--- + +# Second opinion + +Put a question to a **different harness** — a different model — and bring back its take. Copy-paste your session into it as is, no changes. + +## 1. Export the transcript + +Dump this session's conversation to `.scratch/second-opinion/.transcript.md`, `` naming the question (outside a repo, use a temp file). See [`transcripts.md`](transcripts.md) for the extraction recipe for the harness you're running in. + +Append one line: which question is live. "Answer Q{N}" + +The transcript ships everything it contains — command output, file contents, whatever your tool calls returned — to another vendor's CLI. If anything in this session shouldn't travel, say so and let the user decide before you send. + +**Done when:** the file reads as the conversation, in order, including the recommendations you gave along the way. + +## 2. Ask the other harness + +The invocation takes `[harness] [model] [effort]`, each optional. Use what was given; for what wasn't, ask the user which harness to put it to — naming the ones installed here other than the one you're running in — and let the CLI fall back to its own configured default model and effort. + +| Harness | Command | Model | Effort | +| --- | --- | --- | --- | +| Codex | `codex exec -s read-only -o .scratch/second-opinion/.answer.md - < .scratch/second-opinion/.transcript.md` | `-m ` | `-c model_reasoning_effort=` | +| Cursor | `cursor-agent -p --mode ask "$(cat .scratch/second-opinion/.transcript.md)" > .scratch/second-opinion/.answer.md` | `--model ` | `--model '[effort=]'` | +| Claude Code | `claude -p "$(cat .scratch/second-opinion/.transcript.md)" > .scratch/second-opinion/.answer.md` | `--model ` | — | + +Run it read-only, from the repo root, in the background — these take minutes. + +## 3. Report the divergence + +Give the user, in order: + +1. **The answer, verbatim** — the whole of what came back, copied through unedited. +2. **Where it diverges from yours** — the specific claim, weighting, or assumption you disagree on. + +Check any repo fact it asserts that you hadn't, and say what you found. + +Hold both takes open. + +## 4. Resume + +Return to the grilling question and wait for the user's answer. diff --git a/skills/second-opinion/transcripts.md b/skills/second-opinion/transcripts.md new file mode 100644 index 0000000..47713e6 --- /dev/null +++ b/skills/second-opinion/transcripts.md @@ -0,0 +1,35 @@ +# Exporting the current session's transcript + +Each harness persists its own sessions locally. Use the recipe for the one you're running in. + +Keep assistant and user text; reduce tool calls to a one-line marker so the reader can see where legwork happened without drowning in tool output. + +## Claude Code + +Sessions live in `~/.claude/projects//.jsonl`, where `` is the working directory with every `/` and `.` replaced by `-`. The current session is the most recently modified file there. + +```bash +SLUG=$(pwd | sed 's/[/.]/-/g') +SESSION=$(ls -t ~/.claude/projects/"$SLUG"/*.jsonl | head -1) +jq -r ' + select(.type=="user" or .type=="assistant") | select(.isSidechain|not) + | (.message.role // .type) as $role + | ((.message.content // []) | if type=="string" then [{type:"text",text:.}] else . end) + | map(if .type=="text" then .text + elif .type=="tool_use" then "[tool: " + .name + "]" + else empty end) | join("\n") + | select(length>0) | "\n## " + $role + "\n\n" + . +' "$SESSION" > .scratch/second-opinion/.transcript.md +``` + +## Codex + +Sessions live under `~/.codex/sessions////`, most recent last. Same JSONL shape in outline — inspect one line first and adjust the filter to the keys you find. + +## Cursor + +Chats live under `~/.cursor/chats/`. Inspect the store before writing a filter; the layout differs from the JSONL harnesses. + +## If extraction fails + +Don't reconstruct the conversation from memory — a remembered transcript is a summary, and a summary is the briefing this skill exists to avoid. Tell the user extraction failed, and ask them to paste the conversation into a file instead. From 3316693d72245856e1d9cd48828b18af44964c69 Mon Sep 17 00:00:00 2001 From: Graeme Ellis Date: Thu, 30 Jul 2026 07:45:08 -0700 Subject: [PATCH 2/5] Drop the harness command table from second-opinion Flag syntax is CLI trivia the agent can recall or look up, and a table of flags rots. Keep only the three constraints it wouldn't default to: read-only, transcript in as the prompt, answer captured to a file. Co-Authored-By: Claude Opus 5 --- skills/second-opinion/SKILL.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/skills/second-opinion/SKILL.md b/skills/second-opinion/SKILL.md index 2cd56ba..3736fc8 100644 --- a/skills/second-opinion/SKILL.md +++ b/skills/second-opinion/SKILL.md @@ -21,15 +21,9 @@ The transcript ships everything it contains — command output, file contents, w ## 2. Ask the other harness -The invocation takes `[harness] [model] [effort]`, each optional. Use what was given; for what wasn't, ask the user which harness to put it to — naming the ones installed here other than the one you're running in — and let the CLI fall back to its own configured default model and effort. +The invocation takes `[harness] [model] [effort]`, each optional. For what wasn't given, ask the user which harness — naming the ones installed here other than the one you're running in — and let the CLI use its own default model and effort. -| Harness | Command | Model | Effort | -| --- | --- | --- | --- | -| Codex | `codex exec -s read-only -o .scratch/second-opinion/.answer.md - < .scratch/second-opinion/.transcript.md` | `-m ` | `-c model_reasoning_effort=` | -| Cursor | `cursor-agent -p --mode ask "$(cat .scratch/second-opinion/.transcript.md)" > .scratch/second-opinion/.answer.md` | `--model ` | `--model '[effort=]'` | -| Claude Code | `claude -p "$(cat .scratch/second-opinion/.transcript.md)" > .scratch/second-opinion/.answer.md` | `--model ` | — | - -Run it read-only, from the repo root, in the background — these take minutes. +Run it read-only from the repo root, feed the transcript in as the prompt, and capture the answer to `.answer.md`. Background it; these take minutes. ## 3. Report the divergence From 9d2a8ac8c28a6ed456c3d6553b2b9440293b223c Mon Sep 17 00:00:00 2001 From: Graeme Ellis Date: Thu, 30 Jul 2026 07:51:07 -0700 Subject: [PATCH 3/5] Update SKILL.md to refine comparison instructions --- skills/second-opinion/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/second-opinion/SKILL.md b/skills/second-opinion/SKILL.md index 3736fc8..90b392e 100644 --- a/skills/second-opinion/SKILL.md +++ b/skills/second-opinion/SKILL.md @@ -30,11 +30,11 @@ Run it read-only from the repo root, feed the transcript in as the prompt, and c Give the user, in order: 1. **The answer, verbatim** — the whole of what came back, copied through unedited. -2. **Where it diverges from yours** — the specific claim, weighting, or assumption you disagree on. +2. **Compare** — the specific claim, weighting, and assumptions. Check any repo fact it asserts that you hadn't, and say what you found. -Hold both takes open. +Hold both takes open. ## 4. Resume From de9a1e2ed63eec5fa577ae0d9c91878fdbb7e348 Mon Sep 17 00:00:00 2001 From: Graeme Ellis Date: Thu, 30 Jul 2026 08:17:39 -0700 Subject: [PATCH 4/5] Strip skill bodies from the second-opinion transcript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skill bodies arrive as user messages, so the extraction captured this skill's own text. Its closing line — ask the user which harness — became the transcript's last live instruction, and codex answered that instead of the question. Observed on a real run. Also close the transcript with the question in full rather than a bare Answer Q{N}, which wasn't enough to outrank a trailing block of instructions. Co-Authored-By: Claude Opus 5 --- skills/second-opinion/SKILL.md | 2 +- skills/second-opinion/transcripts.md | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/skills/second-opinion/SKILL.md b/skills/second-opinion/SKILL.md index 90b392e..c1695c6 100644 --- a/skills/second-opinion/SKILL.md +++ b/skills/second-opinion/SKILL.md @@ -13,7 +13,7 @@ Put a question to a **different harness** — a different model — and bring ba Dump this session's conversation to `.scratch/second-opinion/.transcript.md`, `` naming the question (outside a repo, use a temp file). See [`transcripts.md`](transcripts.md) for the extraction recipe for the harness you're running in. -Append one line: which question is live. "Answer Q{N}" +Close with a rule and the live question in full. It's the last thing read, and it has to outrank everything above it — a bare "Answer Q2" doesn't. The transcript ships everything it contains — command output, file contents, whatever your tool calls returned — to another vendor's CLI. If anything in this session shouldn't travel, say so and let the user decide before you send. diff --git a/skills/second-opinion/transcripts.md b/skills/second-opinion/transcripts.md index 47713e6..6731f44 100644 --- a/skills/second-opinion/transcripts.md +++ b/skills/second-opinion/transcripts.md @@ -4,6 +4,8 @@ Each harness persists its own sessions locally. Use the recipe for the one you'r Keep assistant and user text; reduce tool calls to a one-line marker so the reader can see where legwork happened without drowning in tool output. +Drop skill bodies. They arrive as user messages but they're instructions to *this* agent, not conversation — and the other harness will read them as its own. Left in, a skill's closing line ("ask the user which harness") outranks your appended question, and the answer comes back addressed to that instead. + ## Claude Code Sessions live in `~/.claude/projects//.jsonl`, where `` is the working directory with every `/` and `.` replaced by `-`. The current session is the most recently modified file there. @@ -18,7 +20,9 @@ jq -r ' | map(if .type=="text" then .text elif .type=="tool_use" then "[tool: " + .name + "]" else empty end) | join("\n") - | select(length>0) | "\n## " + $role + "\n\n" + . + | select(length>0) + | select(test("^Base directory for this skill:|")|not) + | "\n## " + $role + "\n\n" + . ' "$SESSION" > .scratch/second-opinion/.transcript.md ``` From 4f4d20c2f67bc0ff0272c6878ca09920045d19e2 Mon Sep 17 00:00:00 2001 From: Graeme Ellis Date: Thu, 30 Jul 2026 08:25:05 -0700 Subject: [PATCH 5/5] License the second opinion to challenge the frame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ablation on a real question, same transcript and model, one variable: with the clause the other harness listed three premises it disputed; without it the answer stayed inside the frame and audited nothing. Disagreement with the recommendation happened either way — challenging the question did not. Co-Authored-By: Claude Opus 5 --- skills/second-opinion/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/second-opinion/SKILL.md b/skills/second-opinion/SKILL.md index c1695c6..b15d27d 100644 --- a/skills/second-opinion/SKILL.md +++ b/skills/second-opinion/SKILL.md @@ -13,7 +13,7 @@ Put a question to a **different harness** — a different model — and bring ba Dump this session's conversation to `.scratch/second-opinion/.transcript.md`, `` naming the question (outside a repo, use a temp file). See [`transcripts.md`](transcripts.md) for the extraction recipe for the harness you're running in. -Close with a rule and the live question in full. It's the last thing read, and it has to outrank everything above it — a bare "Answer Q2" doesn't. +Close with a rule and the live question in full, then: *and challenge any claim above you think is wrong*. It's the last thing read, and it has to outrank everything above it — a bare "Answer Q2" doesn't. Disagreement comes for free, but a pinned question keeps the other harness inside the frame; that clause is what buys back the licence to attack it. The transcript ships everything it contains — command output, file contents, whatever your tool calls returned — to another vendor's CLI. If anything in this session shouldn't travel, say so and let the user decide before you send.