Skip to content
Back to skills

Curiosity Engine

BSecurity

Structured Q&A rally between the host orchestrating model and a backend model. Both sides must always reply with ANSWER and QUESTION prefixes. Seeded by topic, runs for N rounds.

  • 10 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 27, 2026
ai-agentsrustgoshellbashdebugginggitapibackend

Works with

  • claude code
  • cli
  • api

Security analysis

B84/100
  • mediumUses curl or wget to download content
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 1, 2026

npx -y skills add freibergergarcia/phone-a-friend --skill curiosity-engine --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Curiosity Engine?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Curiosity Engine
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/freibergergarcia-curiosity-engine/badge)](https://www.skillsdirectory.com/skills/freibergergarcia-curiosity-engine)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: curiosity-engine
description: "Structured Q&A rally between the host orchestrating model and a backend model. Both sides must always reply with ANSWER and QUESTION prefixes. Seeded by topic, runs for N rounds."
argument-hint: '--topic "<topic>" [--rounds N] [--backend antigravity|codex|gemini|ollama]'
---

# /curiosity-engine

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, the pi model in pi) and a backend model.
In pi this skill runs as `/skill:curiosity-engine --topic "<topic>"`; the
arguments arrive as the user request that follows these instructions.
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).

## Execution rules

- The host model running this skill is the orchestrator. It serves the
  opening question and answers each round directly. Do NOT call
  `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.
- One backend per relay call. Never pass comma-separated values to `--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`.
- Inside OpenCode, prefix relay invocations with
  `PHONE_A_FRIEND_HOST=opencode` so PaF detects the host deterministically.
- Inside Codex, prefix relay invocations with `PHONE_A_FRIEND_HOST=codex`
  for the same reason. Do not select `codex` as the friend backend from
  Codex; choose `antigravity`, `gemini`, or `ollama` instead. PaF will
  refuse recursive Codex calls.
- Inside pi, no host prefix is needed: pi marks the commands its `bash`
  tool runs with `PI_CODING_AGENT=true`, and PaF refuses `--to pi` there.
- Suppress the working-tree diff on every binary-mode relay (see "Diff
  suppression" below). Curiosity rounds are seeded with self-contained
  prompts; the diff would be noise.
- Do NOT dump repo files or git output (`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 (antigravity, 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.

## Inputs

- Arguments: `$ARGUMENTS`

## Step 0 — Relay mode

```bash
command -v phone-a-friend
```

- If found: set `RELAY_MODE = binary`
- If not found: set `RELAY_MODE = direct`

No hard abort. The skill continues either way.

### Direct call reference

When `RELAY_MODE = direct`, call backend CLIs directly instead of using the
`phone-a-friend` binary:

| Backend | Direct command |
|---------|---------------|
| **Antigravity** | `agy --add-dir "$PWD" --print-timeout 300s --sandbox --mode plan --prompt "$(cat "$PROMPT_FILE")"` |
| **Codex** | `codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "$(cat "$PROMPT_FILE")" < /dev/null` |
| **Gemini** | `gemini --sandbox --approval-mode plan --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'` |

Gemini's `--approval-mode plan` is Gemini Plan Mode, a best-effort read-only restriction: headless Gemini may exit Plan Mode and switch to YOLO. Use Antigravity when enforced read-only behavior is required.

pi is not a `--backend` choice here and has no direct-call row. It is reachable through `/phone-a-friend` in binary mode (`phone-a-friend --to pi`) only.

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.

## Diff suppression

`/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):

```bash
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.

## Step 1 — Parse Arguments

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 antigravity|codex|gemini|ollama` — optional, default `codex`.

If `--backend` value is not `antigravity`, `codex`, `gemini`, or `ollama`: report error and stop.

Set:
- TOPIC = parsed topic string
- TOPIC_SAFE = TOPIC (untrusted text; never splice it into an inline shell command)
- MAX_ROUNDS = parsed rounds (default 3, clamped [1, 6])
- BACKEND = parsed backend (default `codex`)
- ROUND = 1

## Step 2 — Preflight Check

### CLI backends

```bash
command -v agy     # if BACKEND=antigravity
command -v codex   # if BACKEND=codex
command -v gemini  # if BACKEND=gemini
```

### Ollama backend

```bash
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 |
|----------|-----------|--------|
| antigravity | yes    | proceed |
| antigravity | no     | abort: "Antigravity CLI not found. Install from https://antigravity.google/ or use Gemini CLI with API key/Vertex." |
| 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?" |

## Step 3 — Serve Round 1

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, the pi model name in pi).
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:

```bash
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`):
```bash
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`):
```bash
# Antigravity:
agy --add-dir "$PWD" --print-timeout 300s --sandbox --mode plan --prompt "$(cat "$PROMPT_FILE")"
# Codex:
codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "$(cat "$PROMPT_FILE")" < /dev/null
# Gemini (always include -m):
gemini --sandbox --approval-mode plan --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'
```

## Step 4 — Parse Backend Response

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.

### Parse algorithm

1. Look for a line starting with `ANSWER:` — extract everything after it (may be multiline until `QUESTION:` appears).
2. Look for a line starting with `QUESTION:` — extract everything after it to end of response.
3. If both fields found → valid response. Proceed to Step 5.
4. If `QUESTION:` is missing → schema violation. Execute re-prompt (see Step 4a).
5. If `ANSWER:` is missing → schema violation. Treat the same as missing `QUESTION:` — execute re-prompt (Step 4a).

### Step 4a — Re-prompt on schema violation

Send one correction relay if `ANSWER:` or `QUESTION:` is missing:

First create `REPROMPT_FILE` with a quoted heredoc:

```bash
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`):
```bash
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`):
```bash
# Antigravity:
agy --add-dir "$PWD" --print-timeout 300s --sandbox --mode plan --prompt "$(cat "$REPROMPT_FILE")"
# Codex:
codex exec -C "$PWD" --skip-git-repo-check --sandbox read-only "$(cat "$REPROMPT_FILE")" < /dev/null
# Gemini:
gemini --sandbox --approval-mode plan --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).

## Step 5 — Display Round and Continue

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.

## Step 6 — Final Synthesis

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>
```

## Gemini model selection

For BACKEND=antigravity, omit `--model` by default. Antigravity model names
are UI display names and PaF forwards explicit `--model` values unchanged
only when the user asks for one. Antigravity is read-only and supports native session resume in PaF.

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):

```bash
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):
```bash
gemini --sandbox --approval-mode plan --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.

## Constraints

- MAX_ROUNDS clamped to [1, 6]. Never exceed.
- Both sides must always produce ANSWER: and QUESTION:. The orchestrator never breaks the schema.
- One re-prompt allowed per round on schema violation. Two strikes = early termination.
- No nested curiosity-engine sessions.
- phone-a-friend is used as a black box — do not modify its internals.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…