CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

quinnod345/ponder

Convergent multi-pass deliberation and code review ledger that enumerates units, grades every unit against a lens rubric, runs four-perspective deliberation (Skeptic, Builder, Dreamer, Empath), records append-only finding deltas, verifies phantom-unit removals across all historical paths, and enforces 1.00 coverage and zero-churn convergence before stopping. Use when the user says "ponder <anything>" ("ponder the UI", "ponder our error handling", "ponder this PR"), "run a ponder audit", "grade every unit with the ponder ledger", "run four-voice Skeptic/Builder/Dreamer/Empath deliberation", or asks for a coverage-gated multi-pass lens audit across files or screens where stopping early would miss units. Also use to resume or recover a ponder session in flight - "where were we in the ponder", "carry on pondering", or an existing .ponder/ directory - by running ponder status rather than starting over.

Quality

100%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

harness-notes.mdreferences/

Harness notes - Claude Code, Codex, and OpenClaw

This skill is one directory, symlinked into every agent harness (~/.claude/skills/ponder, ~/.agents/skills/ponder, and ~/.codex/skills/ponder). Everything here is the set of differences that actually change what you type.

Install

One source of truth, symlinked everywhere:

git clone https://github.com/Quinnod345/ponder.git ~/code/ponder
~/code/ponder/install.sh

It links skill/ponder into ~/.claude/skills/ponder, ~/.agents/skills/ponder (and ~/.codex/skills/ponder when ~/.codex exists) and bin/ponder into ~/.local/bin, then runs ponder doctor. Re-running is safe - it replaces its own symlinks and refuses to clobber a real directory. The equivalent by hand:

ln -s ~/code/ponder/skill/ponder ~/.claude/skills/ponder
ln -s ~/code/ponder/skill/ponder ~/.agents/skills/ponder
chmod +x ~/code/ponder/bin/ponder
ln -s ~/code/ponder/bin/ponder ~/.local/bin/ponder

bin/ponder is a POSIX sh shim, not JavaScript. That is deliberate: an extensionless JS file trips Node's module resolution. It walks its own symlink chain before exec node …/lib/cli.js "$@", because without that a ponder symlinked onto PATH would look for ~/.local/lib/cli.js. Do not "fix" either part.

Front matter

Only two keys, because only two keys are valid in both harnesses:

---
name: ponder
description: …
---

Codex additionally understands an optional metadata: block - disable-model-invocation (~/.codex/skills/zoom-out/SKILL.md), argument-hint (~/.codex/skills/handoff/SKILL.md) and short-description (~/.codex/vendor_imports/skills/skills/.curated/linear/SKILL.md). Claude Code reads the same keys at the top level instead, not nested - compare its own copy of zoom-out, which carries a bare disable-model-invocation: true. So a metadata: block would make the two installs behave differently from one file, and this skill has none.

The other rule the front matter has to obey is YAML's, and it bites in exactly one place: a plain scalar cannot contain : (colon-space). The description is a long prose line full of quoted trigger phrases, and one stray colon-space in it makes the whole block unparseable - which in both harnesses means the skill silently does not load rather than erroring. After any edit to it:

python3 -c "import yaml,sys; d=yaml.safe_load(open(sys.argv[1]).read().split('---',2)[1]); \
  assert set(d) == {'name','description'}, d.keys(); print('ok:', d['name'], len(d['description']))" \
  ~/code/ponder/skill/ponder/SKILL.md

Checking the install

ls -l ~/.claude/skills/ponder ~/.codex/skills/ponder    # both should point at ~/code/ponder/skill/ponder
head -4 ~/.claude/skills/ponder/SKILL.md                 # front matter intact through the symlink
ponder doctor                                            # engine reachable, and what it can see

In Claude Code, the skill then appears in the available-skills list; /ponder invokes it, as does the Skill tool with skill: "ponder". In Codex, skills under $CODEX_HOME/skills (default ~/.codex/skills) are picked up on the next turn after installation, not the current one - if it does not fire immediately, that is why.

Invocation

Both harnesses model-invoke skills from the description, and both take an explicit /ponder. The practical difference: Claude Code surfaces the skill list to the model every turn, so a trigger phrase reliably fires mid-conversation. In Codex, a skill invoked deep in a long session sometimes needs the explicit /ponder - if the user says "keep pondering" and nothing loads, read the SKILL.md by path and carry on rather than debating it:

cat ~/code/ponder/skill/ponder/SKILL.md

Subagents and fan-out

Claude CodeCodex
In-process subagentsAgent tool. Multiple calls in one message run in parallel. Returns text to the parent.None.
Very large sweepsWorkflow tool - handles batching, retries and collection better than hand-rolled parallel agents.None.
Subprocess agentspossible, rarely worth itcodex exec - the primary mechanism
BackgroundBash tool run_in_background, plus Monitor to wait on a condition& + wait, or nohup … & and poll the output file

codex exec flags that matter for fan-out:

codex exec -C "$PWD" \
  --sandbox workspace-write \
  --skip-git-repo-check \
  -o .ponder/tmp/batch-3.txt \
  "…prompt…"
  • -C, --cd <DIR> - working root for the child.
  • -s, --sandbox read-only|workspace-write - the child's own sandbox, independent of yours.
  • --add-dir <DIR> - extra writable directory, needed if the session state lives outside the workspace.
  • -o, --output-last-message <FILE> - writes the child's final message to a file. This is what makes fan-out collectible. Have the child write its JSONL to disk anyway and return only the path plus a count; then -o is your receipt, not your payload.
  • --output-schema <FILE> - a JSON Schema for the final response, when you want the shape enforced.
  • --json - event stream as JSONL on stdout, for progress monitoring.
  • --ephemeral - no session files on disk, for throwaway batches.

Two of those are load-bearing rather than optional, and both were checked on this machine against codex-cli 0.147.0:

  • --skip-git-repo-check is not paranoia. Without it the child refuses before doing any work - Not inside a trusted directory and --skip-git-repo-check was not specified, exit 1. Fan out over a subject that is not a git checkout and every batch dies at once, identically and instantly, which reads like a prompt problem and is not one.
  • codex exec reads stdin even when the prompt is an argument (Reading additional input from stdin...). Backgrounded with & it inherits the parent's stdin, so redirect it: … </dev/null &. From /dev/null it takes the EOF and carries on; from a pipe nobody closes it does not.

codex exec starts a fresh session. It inherits none of your context, and it loads its own AGENTS.md chain from -C. Everything the batch needs goes in the prompt or in a file it can read.

Concatenating batch outputs safely

Two failure modes bite when collecting JSONL from parallel batch agents:

  1. Always call process.stdin.setEncoding("utf8") when wrapping JSONL into findings.json. Without it, stdin chunks arrive as raw buffers and each buffer is stringified independently, so a multi-byte UTF-8 character straddling a 64 KB read boundary is corrupted into U+FFFD replacement characters with a clean exit code.
  2. Stage batch files in .ponder/tmp/batch/ and write the concatenated file to .ponder/tmp/. Never run cat .ponder/tmp/deltas-*.jsonl > .ponder/tmp/deltas-2.jsonl in the same directory: on a retry, the glob matches its own output file deltas-2.jsonl, which either fills the disk in a self-reading loop (if batch 1 sorts before the target) or truncates the file before reading it. Always keep batch shards in .ponder/tmp/batch/deltas-2-*.jsonl and redirect to .ponder/tmp/deltas-2.jsonl.

The session lock

The engine locks. Do not architect around a lost-update race - there isn't one. Earlier revisions of this file said the opposite and told you to defend against a hazard that does not exist. Here is what actually happens, read out of lib/session.js and reproduced on this machine.

lockSession() takes an exclusive lock on the session directory before any command that writes state: units, predict, grade, deliberate, delta, judge, decide, apply, abort, and verify --repair. The read-only commands - status, report, list, doctor, and a plain verify - take no lock, so you can always ask where a session is even while a pass is being written into it. verify is the only command whose locking depends on a flag, for the obvious reason: with --repair it rewrites the state files, without it it writes nothing at all.

Mechanismopen(.ponder/<slug>/.lock, 'wx') - atomic create-exclusive. The winner writes its pid and a timestamp into the file; the file's existence is the lock
Contending commandpolls every 60 ms (Atomics.wait, no CPU spin) for up to 10 seconds
Wait exhaustedexits 1: another ponder command is already working on <slug>. - and names the lock file
Stale reclaima .lock whose mtime is older than 120 seconds is deleted and taken. This is what covers a kill -9
Normal releaseunlink when the command finishes, plus a process exit hook for process.exit()
Unlockable storeif the lock cannot be created for any reason other than EEXIST, the command runs without one. A safety net must not become a new way to fail

What the loser of a real race sees. Two ponder delta calls fired at the same pass, measured:

A rc=2   error out of order: "ponder delta" satisfies revise, but this session owes judge.
B rc=0   ponder delta - pass 1, 1 delta applied

The lock serialises them and the phase machine finishes the job. B takes the lock, applies its batch, and advances the phase from revise to judge. A waits a few milliseconds, takes the lock, reloads session.json from disk - and now owes judge, so its ponder delta is out of order and exits 2 without applying anything. The ledger grew by exactly one line, findings.json agrees with it, and the phase advanced exactly once. Two ponder decide at once record one pass, not two. Two ponder start on one slug: mkdir without recursive is the atomic claim, so the second exits 1 with session "<slug>" already exists.

The conformance suite pins this: 45 checks in concurrency: racing deltas have exactly one winner and 14 more in concurrency: racing decisions and starts have one winner too, run over three fresh sessions because a lock-free engine only loses the race some of the time. Making lockSession a no-op fails both.

What this means for fan-out. The rule is unchanged - only the parent runs ponder - but the reason is sequencing, not corruption. A race has a winner and a loser, and the loser's work is refused, not merged. A subagent that calls ponder delta and does not check its exit code will report success while its entire batch went nowhere. So: batch agents write JSONL to disk and return a path plus a count, the parent concatenates, and the parent makes one call. That also keeps f-NNNN assignment and the ledger in one place, which is where they belong.

Two practical notes. The 10-second wait is generous against a command that finishes in well under a second, but a command stuck on a slow OCA bridge call (5 s per call) can outlast it - if a queued command exits 1 on the lock, re-run it rather than deleting the file. And delete a .lock by hand only when you know the process that made it is gone; otherwise wait, or let the 120-second stale reclaim do it for you.

Sandboxing, and whether psql is reachable

Claude Code on this machine: the Bash tool is sandboxed, and the sandbox does not block the OCA. Verified: psql "postgres://localhost/oneiro" -tAc "select 1" returns 1 from inside the tool. Writes into the project tree are fine. If a bridge call ever does get blocked, ponder degrades silently by design - check .ponder/<slug>/oca.log, not the engine.

Codex under workspace-write or read-only sandboxes (including any codex exec -s workspace-write you spawn yourself): The three points below follow from Codex's documented sandbox semantics. Check with ponder doctor rather than assuming:

  • Network is off under workspace-write by default. psql to postgres://localhost/oneiro over TCP should fail. The bridge returns null, the loop runs identically, and you lose prior-pondering recall and the OCA writes. That is the designed degradation, not a bug - but do not promise the user cross-session memory from a run you made in that sandbox.
  • Writes outside the workspace are blocked. The ~/.ponder/<slug>/ fallback should fail. Keep the session inside the project with ponder start --project "$PWD", or widen the child's sandbox when you spawn it - codex exec --add-dir ~/.ponder. --add-dir is a codex exec flag, not a ponder one; ponder start --add-dir exits 1 with unknown flag --add-dir.
  • read-only cannot run a pass at all - every command in the loop writes state. The inspection commands that write nothing (status, list, verify without --repair) should still work, which is enough to find out where a session stands but not to move it.

ponder doctor settles all three in one call: it prints whether the OCA is reachable and which store roots are writable. Run it inside the child sandbox, not outside it.

Rule of thumb: run ponder doctor as the first command in any unfamiliar harness. It reports node, psql, the DSN, OCA reachability and where state will be written, which is exactly the set of things that differ between these two environments.

Context and compaction

Claude Code compacts long sessions, and a compaction is the single most likely way a long ponder dies. Codex has no compaction guarantee and a smaller effective working window in practice. The mitigation is the same in both and it is already built in: state lives in files, and every command prints NEXT:. After any loss, ponder status tells you what you owe; .ponder/<slug>/ledger.jsonl tells you everything every pass did and why.

Practical consequence for Codex specifically: prefer sequential batches that write JSONL to disk and drop the detail, over holding 40 units' worth of code in one context. The disk is the memory.

Paths

Skill-relative references (references/protocol.md) resolve differently per harness - ~/.claude/skills/ponder/references/… vs ~/.codex/skills/ponder/references/…. Both are symlinks to the same place, so when in doubt use the repo path, which is always correct:

~/code/ponder/skill/ponder/references/protocol.md
~/code/ponder/lenses/ui.md

Session state goes in <project>/.ponder/. It is plain JSON and JSONL. Commit it when the ledger is worth keeping across branches and machines - that history is the point of the append-only design - otherwise add .ponder/ to .gitignore. Do not commit .ponder/tmp/.

Known bites

  • codex exec hangs with no output, including --version. Seen on macOS when codex is launched over SSH or tmux: a macOS Gatekeeper/amfid block keyed to the install path. Run a clean-path copy of the binary instead.
  • A queued command that exits 1 on the lock. Not corruption - the session lock did its job and the wait ran out. Re-run the command. See The session lock; do not delete a .lock file unless the process that wrote it is gone.
  • A skill edit that does not take effect. Both harnesses read SKILL.md at load; edit the repo copy (~/code/ponder/skill/ponder/SKILL.md) and start a new turn - in Codex, a new session.
  • The OCA's cognitive-loop process on :3333 is not running. Nothing in ponder depends on it; the bridge shells out to psql directly. If something suggests otherwise, it is wrong.

SKILL.md

tile.json