From phone-a-friend
Runs structured Q&A ping-pong rallies between host model and backends (codex, gemini, ollama) seeded by topic for 3-6 rounds of alternating answers and questions.
npx claudepluginhub freibergergarcia/phone-a-friend--topic "<topic>" [--rounds N] [--backend codex|gemini|ollama]This skill uses the workspace's default tool permissions.
A structured ping-pong Q&A game between the host orchestrating model (the
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Executes ctx7 CLI to fetch up-to-date library documentation, manage AI coding skills (install/search/generate/remove/suggest), and configure Context7 MCP. Useful for current API refs, skill handling, or agent setup.
Share bugs, ideas, or general feedback.
A structured ping-pong Q&A game between the host orchestrating model (the agent running this skill — Claude in Claude Code, the OpenCode model in OpenCode) and a backend model. Both sides MUST produce an ANSWER: and a QUESTION: every round. The game is seeded with a topic and runs for N rounds (default 3, max 6).
phone-a-friend --to claude (or any other backend) to generate the
orchestrator's questions or answers — that would relay the orchestrator
role to a different model.--to
(e.g. phone-a-friend --to codex,gemini).curiosity-engine is a host slash command / Agent Skill, not a PaF CLI
subcommand. Never run phone-a-friend curiosity-engine.--backend is an argument to this skill, not a PaF CLI flag. Do not pass
--backend to phone-a-friend.PHONE_A_FRIEND_HOST=opencode so PaF detects the host deterministically.git show, git diff,
git status, etc.) into the relay prompt. Curiosity rounds are seeded
with self-contained prompts; if the round needs file context,
repo-aware backends (codex, gemini) can read the repo via
--repo "$PWD". For ollama (no repo file access), pick a repo-aware
backend instead, or ask before sending a minimal excerpt. Inlining
repo content can leak uncommitted edits or committed secrets and is
not needed for a Q&A rally. The opening question and round
transcripts are narrative context that the orchestrator generates and
inlines into the relay prompt; that is the intended use, not file
dumping.$ARGUMENTScommand -v phone-a-friend
RELAY_MODE = binaryRELAY_MODE = directNo hard abort. The skill continues either way.
When RELAY_MODE = direct, call backend CLIs directly instead of using the
phone-a-friend binary:
| Backend | Direct command |
|---|---|
| Codex | codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "$(cat "$PROMPT_FILE")" < /dev/null |
| Gemini | gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "$(cat "$PROMPT_FILE")" |
| Ollama | PROMPT_JSON="$(jq -Rs . < "$PROMPT_FILE")"; curl -s http://localhost:11434/api/chat -H "Content-Type: application/json" -d "{\"model\":\"<model>\",\"messages\":[{\"role\":\"user\",\"content\":${PROMPT_JSON}}],\"stream\":false}" | jq -r '.message.content' |
In direct mode, build PROMPT_FILE from the relay prompt using this
template and the quoted-heredoc rule:
You are helping another coding agent by reviewing or advising on work in a local repository.
Repository path: <repo-path>
Use the repository files for context when needed.
Respond with concise, actionable feedback.
Request:
<relay-prompt>
No "Additional Context" section is needed for curiosity-engine (prompts are self-contained).
Note: do NOT pass PaF flags like --no-include-diff, --fast, or
--session in direct mode. They are CLI flags on the phone-a-friend
binary; the underlying backend CLIs do not accept them.
/curiosity-engine rounds use self-contained prompts; the working-tree diff
would be irrelevant noise. PaF reads defaults.include_diff from user
config, so without explicit suppression a user with include_diff = true
would silently leak the diff into every relay round.
The cleanest flag is --no-include-diff, added in phone-a-friend v2.2.0.
Older binaries reject the flag with unknown option '--no-include-diff'.
Probe once before Round 1, then reuse the gate across every binary-mode
relay (initial round, follow-up rounds, and the schema re-prompt):
if phone-a-friend relay --help 2>/dev/null | grep -q -- '--no-include-diff'; then
PAF_NO_DIFF="--no-include-diff"
else
export PHONE_A_FRIEND_INCLUDE_DIFF=false
PAF_NO_DIFF=""
fi
Append $PAF_NO_DIFF to every binary-mode phone-a-friend invocation in
the steps below. The env var fallback works in v1.7.2 and later; the
explicit flag is preferred when available.
Extract --topic, --rounds, and --backend from $ARGUMENTS.
--topic <string> — required. Everything after --topic up to the next flag. If missing, ask the user: "What topic should the Curiosity Engine explore?" Do not proceed until provided.--rounds N — optional, default 3, clamp to [1, 6].--backend codex|gemini|ollama — optional, default codex.If --backend value is not codex, gemini, or ollama: report error and stop.
Set:
codex)command -v codex # if BACKEND=codex
command -v gemini # if BACKEND=gemini
curl -sf http://localhost:11434/api/tags
If reachable and RELAY_MODE = direct: parse the JSON response to extract
model names from models[].name. Set OLLAMA_SELECTED_MODEL to the first
model in the list. If the list is empty, abort: "Ollama server is running but
has no models pulled. Install one with: ollama pull <model-name>". Report
the selected model to the user: "Ollama: using model <name>".
If RELAY_MODE = binary, the binary handles model selection internally.
| BACKEND | Available | Action |
|---|---|---|
| codex | yes | proceed |
| codex | no | abort: "codex CLI not found. Install: npm install -g @openai/codex" |
| gemini | yes | proceed |
| gemini | no | abort: "gemini CLI not found. Install: npm install -g @google/gemini-cli" |
| ollama | reachable | proceed (discover model if direct mode) |
| ollama | not reachable | abort: "Ollama not reachable at localhost:11434. Is Ollama running?" |
The orchestrating agent (the host model running this skill) serves first. It produces the opening move directly, without relaying to any backend:
ANSWER: N/A — I'm serving first.
QUESTION: <orchestrator's opening question on TOPIC — make it genuinely curious and specific>
Display to user:
--- Round 1 of <MAX_ROUNDS> | Topic: <TOPIC> ---
🤖 <orchestrator> QUESTION: <question>
<orchestrator> is the host model's display label (e.g., "Claude" in
Claude Code, the OpenCode model name in OpenCode). Pick one that the user
will recognize.
Then relay to backend. First build PROMPT_FILE so untrusted text such as
TOPIC and QUESTION is passed as data, not spliced into an inline shell
command:
PROMPT_FILE="$(mktemp)"
trap 'rm -f "$PROMPT_FILE" "${REPROMPT_FILE:-}"' EXIT
{
printf '%s\n' 'You are playing The Curiosity Engine — a structured Q&A rally with another agent.'
printf 'Topic: %s\n' "$TOPIC_SAFE"
printf 'Round: 1 of %s\n\n' "$MAX_ROUNDS"
printf '%s\n' "The orchestrating agent's question for you:"
printf '%s\n\n' "$QUESTION"
cat <<'PAF_CURIOSITY_PROMPT_EOF'
You MUST respond in EXACTLY this format — no exceptions, no extra text:
ANSWER: <your answer to the orchestrator's question, 2-4 sentences>
QUESTION: <a new question for the orchestrator on the same topic, that you are genuinely curious about>
Do not add any text before ANSWER: or after the QUESTION line.
PAF_CURIOSITY_PROMPT_EOF
} > "$PROMPT_FILE"
Binary mode (RELAY_MODE = binary):
phone-a-friend --to <BACKEND> --repo "$PWD" --sandbox read-only --fast $PAF_NO_DIFF [--model <model>] --prompt "$(cat "$PROMPT_FILE")"
Direct mode (RELAY_MODE = direct):
# Codex:
codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "$(cat "$PROMPT_FILE")" < /dev/null
# Gemini (always include -m):
gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "$(cat "$PROMPT_FILE")"
# Ollama (use OLLAMA_SELECTED_MODEL from Step 2):
PROMPT_JSON="$(jq -Rs . < "$PROMPT_FILE")"
curl -s http://localhost:11434/api/chat -H "Content-Type: application/json" \
-d "{\"model\":\"<OLLAMA_SELECTED_MODEL>\",\"messages\":[{\"role\":\"user\",\"content\":${PROMPT_JSON}}],\"stream\":false}" \
| jq -r '.message.content'
If the relay call (binary or direct) produces no output, empty stdout, or a
non-zero exit code:
Display: ⚠️ Relay call failed for round <ROUND>. Ending game early.
Jump to Step 6 (Synthesis).
After each relay call, parse the response for ANSWER: and QUESTION: fields.
ANSWER: — extract everything after it (may be multiline until QUESTION: appears).QUESTION: — extract everything after it to end of response.QUESTION: is missing → schema violation. Execute re-prompt (see Step 4a).ANSWER: is missing → schema violation. Treat the same as missing QUESTION: — execute re-prompt (Step 4a).Send one correction relay if ANSWER: or QUESTION: is missing:
First create REPROMPT_FILE with a quoted heredoc:
REPROMPT_FILE="$(mktemp)"
cat > "$REPROMPT_FILE" <<'PAF_CURIOSITY_REPROMPT_EOF'
Your previous response did not follow the required format.
You MUST respond with EXACTLY this structure:
ANSWER: <your answer>
QUESTION: <your question for the orchestrator>
No other text. Try again.
PAF_CURIOSITY_REPROMPT_EOF
Binary mode (RELAY_MODE = binary):
phone-a-friend --to <BACKEND> --repo "$PWD" --sandbox read-only --fast $PAF_NO_DIFF [--model <model>] --prompt "$(cat "$REPROMPT_FILE")"
Direct mode (RELAY_MODE = direct):
# Codex:
codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "$(cat "$REPROMPT_FILE")" < /dev/null
# Gemini:
gemini --sandbox --yolo --include-directories "$PWD" --output-format text -m <model> --prompt "$(cat "$REPROMPT_FILE")"
# Ollama:
REPROMPT_JSON="$(jq -Rs . < "$REPROMPT_FILE")"
curl -s http://localhost:11434/api/chat -H "Content-Type: application/json" \
-d "{\"model\":\"<OLLAMA_SELECTED_MODEL>\",\"messages\":[{\"role\":\"user\",\"content\":${REPROMPT_JSON}}],\"stream\":false}" \
| jq -r '.message.content'
Parse again. If still missing QUESTION: → end game early. Display:
⚠️ <BACKEND> broke the chain on round <N> (missing QUESTION: after re-prompt).
Ending game early. Running synthesis on completed rounds.
Jump to Step 6 (Synthesis).
Display backend's response:
🔵 <BACKEND> ANSWER: <answer>
QUESTION: <question>
If this was the final round (ROUND == MAX_ROUNDS) → jump to Step 6 (Synthesis).
Otherwise, increment ROUND. The orchestrating agent (the host model) now responds directly — no relay:
🤖 <orchestrator> ANSWER: <orchestrator's genuine answer to backend's question, 2-4 sentences>
QUESTION: <orchestrator's new question for backend on TOPIC>
Relay the orchestrator's question to backend using this template (same structure as Step 3, substituting current values):
You are playing The Curiosity Engine — a structured Q&A rally with another agent.
Topic: <TOPIC>
Round: <ROUND> of <MAX_ROUNDS>
The orchestrating agent's question for you:
<QUESTION>
You MUST respond in EXACTLY this format — no exceptions, no extra text:
ANSWER: <your answer to the orchestrator's question, 2-4 sentences>
QUESTION: <a new question for the orchestrator on the same topic, that you are genuinely curious about>
Do not add any text before ANSWER: or after the QUESTION line.
Repeat Step 4 and Step 5 until MAX_ROUNDS reached or early termination.
Orchestrator discipline: the host model ALWAYS provides both ANSWER: and QUESTION: — never skips either field, never breaks the schema itself.
If ROUND == 1 and no backend response was ever successfully parsed (zero completed rounds):
Display: No rounds completed — cannot synthesize. Check backend availability and try again.
Stop.
Present the full session summary:
## Curiosity Engine — Session Complete
**Topic:** <TOPIC>
**Backend:** <BACKEND>
**Rounds completed:** <N> of <MAX_ROUNDS>
**Status:** <Converged naturally | Early termination — <BACKEND> broke chain on round N>
---
### Full Rally Transcript
<all rounds, formatted as displayed during play>
---
### Most Interesting Exchange
<orchestrator picks the sharpest Q&A pair from the transcript and explains in 2-3 sentences why it was the most interesting — what tension, insight, or surprise it revealed>
---
### Open Threads
<2-3 questions raised during the rally that weren't followed up on, worth exploring in a future session>
By default, omit --model for --to gemini and let Gemini CLI's
auto-routing pick. Set --model only when reproducibility, specific
capability, or debugging requires a pin.
Binary mode (preferred — when an explicitly pinned model returns a strong
404 / ModelNotFoundError, PaF caches it as unavailable for 24h at
~/.config/phone-a-friend/gemini-models.json and fails fast on subsequent
calls; no auto-substitution):
phone-a-friend --to gemini --repo "$PWD" --sandbox read-only --fast $PAF_NO_DIFF --prompt "$(cat "$PROMPT_FILE")"
To bypass the cache: PHONE_A_FRIEND_GEMINI_DEAD_CACHE=false. Or delete the
cache file to clear it.
Direct mode (no PaF wrapper — orchestrator handles retry):
gemini --sandbox --yolo --include-directories "$PWD" --output-format text --prompt "$(cat "$PROMPT_FILE")"
In direct mode, on capacity/transient errors (429, 500, 503), retry with a
different model before treating as round failure. On ModelNotFoundError,
surface immediately. Do NOT use aliases like auto, pro, or flash —
either omit --model entirely or pass a concrete model name.