agentleFS
littlebearappslittlebearapps/untether/.claude/skills/codex-opencode-pi/SKILL.md

codex-opencode-pi

Skill72 starsChanged 37 days agoMITView on GitHub

Codex CLI, OpenCode CLI, and Pi CLI runner protocols for Untether. Covers JSONL event types, event translation, resume mechanisms, and key differences between engines. All three are non-interactive (no control channel).

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 0 are distinctive, and 4 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.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Prefer item with phase == "final_answer"

    line 89
  2. Very specific:

    If a session header arrives with a UUID, the resume token is promoted to the 8-char short ID

    line 210
  3. Very specific:

    Add reference docs in docs/reference/runners/myengine/

    line 253
  4. Very specific:

    Add tests mirroring existing tests/test_*_runner.py patterns

    line 254

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill 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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Codex, OpenCode, and Pi Runner Protocols

These three engines are non-interactive only — no control channel, no permission prompts. They extend JsonlSubprocessRunner and use its run_impl (ClaudeRunner replaces it; OpenCodeRunner only wraps it with a pre-spawn version check, #970).

A failed resumed run keeps the chat's saved session unless the error names the resume itself (_RESUME_FAILURE_RE in runner_bridge.py: Codex no rollout found, OpenCode Session not found, Pi No session found, …) — none of the three reports num_turns, so the Claude-style 0-turn rule can't apply (#952). A new "session not found" wording must be added there.

Quick comparison

AspectCodexOpenCodePi
CLIcodex exec --jsonopencode run --format jsonpi --print --mode json
Event modelTurn-based (items in turns)Step-based (tools in steps)Agent-based (messages + tools)
Resume line`codex resume <thread_id>``opencode --session ses_XXX``pi --session <token>`
Resume tokenthread_id (UUID)ses_XXXX (26+ chars)UUID short (8 chars) or path
Final answeragent_message itemAccumulated text eventsmessage_end assistant content
Error signalturn.failederror event / missing step_finishstopReason in message_end

Codex

Key files

FilePurpose
src/untether/runners/codex.pyCodexRunner implementation
src/untether/schemas/codex.pymsgspec structs for Codex events
docs/reference/runners/codex/exec-json-cheatsheet.mdJSONL event shapes
docs/reference/runners/codex/untether-events.mdEvent mapping spec

CLI invocation

codex [extra_args…] [--profile P] [--model MODEL] [-c model_reasoning_effort=LEVEL] \
  exec --json --skip-git-repo-check --color=never \
  [--sandbox read-only] [resume <thread_id> | resume --last] -
  • Prompt on stdin (trailing - means read stdin)
  • Resume: resume <thread_id> - (resume --last - for /continue)
  • Safe mode is genuinely read-only: --sandbox read-only (exec-level, so it outranks a root-level sandbox flag), only in safe permission mode and before resume (codex exec resume has no --sandbox); no --ask-for-approval — codex exec forces approval never itself (#830)
  • extra_args sit at the root (before exec) and are checked by runners/extra_args_guard.py: bypass/sandbox-escape flags (--yolo, --sandbox danger-full-access, -c …danger…, -C/--cd, …) disable the engine at load (#209)
  • --skip-git-repo-check --color=never for clean output

JSONL events

EventUntether mapping
thread.startedStartedEvent(resume=thread_id)
item.startedActionEvent(phase="started")
item.updatedActionEvent(phase="updated")
item.completedActionEvent(phase="completed")
turn.completedCompletedEvent(ok=True, answer=final_answer)
turn.failedCompletedEvent(ok=False, error=message)
error (transient)Progress note (reconnect handling)

Item types

Item typeActionKindNotes
command_executioncommandok = (status=="completed" && exit_code==0)
mcp_tool_calltoolTitle: server.tool
file_changefile_changedetail.changes = [{path, kind}]
web_searchweb_searchTitle: query / url / pattern from the untyped action; placeholder web search on started (#419)
todo_listnotedetail.done, detail.total
reasoningnoteReasoning text
agent_message(not emitted)Stored as final answer candidate
error (item)warningNon-fatal warning: ok=True, title prefixed ⚠️ (one ⚠️ row, not ✗); Codex emits each config warning twice, so a repeated message is dropped per run (#987)

Final answer selection

Multiple agent_message items may appear. Selection:

  1. Prefer item with phase == "final_answer"
  2. Fall back to last unnamed agent_message
  3. Used in CompletedEvent.answer

Config keys

[codex]
profile = "Codex"        # Codex profile name
extra_args = []           # additional CLI flags

OpenCode

Key files

FilePurpose
src/untether/runners/opencode.pyOpenCodeRunner implementation
src/untether/schemas/opencode.pymsgspec structs for OpenCode events
docs/reference/runners/opencode/runner.mdRunner spec
docs/reference/runners/opencode/stream-json-cheatsheet.mdJSONL event shapes
docs/reference/runners/opencode/untether-events.mdEvent mapping spec

CLI invocation

opencode run --format json [--session SESSION_ID] [--model MODEL] -- <prompt>
  • Prompt as positional arg after --
  • Resume: --session ses_XXX
  • Session IDs: ses_ prefix + 20+ chars
  • Supported CLI: OpenCode 1.x (npm opencode-ai) only (#970). run_impl probes opencode --version off the event loop (cached per binary path + mtime; failed probes aren't cached) and refuses 2.x (@opencode/cli, which runs prompts on a shared background service outside Untether's process control) with one completed_error carrying PRESPAWN_BLOCKED_KEY, so nothing spawns and the saved session is kept. An unparsable version fails open (opencode.version.unknown). Tests stub the probe via an autouse fixture in tests/conftest.py
  • run auto-rejects ask permissions (never auto-approves; only --dangerously-skip-permissions, which Untether doesn't pass) and denies question/plan_enter/plan_exit. OpenCode does auto-compact, but run --format json doesn't report it (#969)

JSONL events

EventUntether mapping
step_start (first, with sessionID)StartedEvent(resume=sessionID)
tool_use (status="completed")ActionEvent(phase="completed", ok=exit==0)
tool_use (status="error")ActionEvent(phase="completed", ok=False)
textFinal-answer part (no action): each event is one complete part, keyed by part.id and joined with a blank line (#955)
step_finish (reason="stop")CompletedEvent(ok=True, answer=text)
errorCompletedEvent(ok=False, error=message)

Tool mapping

ToolActionKind
bash, shellcommand
edit, write, multieditfile_change
read, glob, greptool
websearch, web_search, webfetch, web_fetchweb_search
todowrite, todoreadnote
tasktool
(other)tool

Usage

Each step_finish's cost and tokens accumulate into OpenCodeStreamState; _build_usage() puts them on CompletedEvent.usage (total_cost_usd + usage{input,output,reasoning,cache_*}_tokens). The bridge accumulates them per session for /usage and /export (#417).

Config keys

[opencode]
model = "anthropic/claude-sonnet-5-5"   # must be OpenCode's provider/model form; bare names fail

Pi

Key files

FilePurpose
src/untether/runners/pi.pyPiRunner implementation
src/untether/schemas/pi.pymsgspec structs for Pi events
docs/reference/runners/pi/runner.mdRunner spec
docs/reference/runners/pi/stream-json-cheatsheet.mdJSONL event shapes
docs/reference/runners/pi/untether-events.mdEvent mapping spec

CLI invocation

pi --print --mode json [--session SESSION_PATH] \
  [--provider PROVIDER] [--model MODEL] <prompt>
  • Prompt as positional arg (prefixed with space if starts with -)
  • Resume: --session <token> (short ID or full path)
  • Minimum version: 0.45.1
  • Environment: NO_COLOR=1, CI=1 (set by runner)

JSONL events

EventUntether mapping
sessionSession ID extraction, possible ID promotion
agent_startStartedEvent(resume=session_token)
tool_execution_startActionEvent(phase="started")
tool_execution_endActionEvent(phase="completed", ok=!isError)
message_end (assistant)Final answer + usage stored
agent_endCompletedEvent(ok=..., answer=last_text)

Session ID promotion

Pi has a unique resume mechanism:

  1. For new runs, Untether generates a session .jsonl file path
  2. If a session header arrives with a UUID, the resume token is promoted to the 8-char short ID
  3. allow_id_promotion flag ensures this only happens once
  4. This gives a user-friendly resume token instead of a long path

Session path format: ~/.pi/agent/sessions/--<sanitized-cwd>--/<date>-<uuid>.jsonl

Tool mapping

ToolActionKindTitle source
bashcommandargs.command
edit, writefile_changeargs.path
read, grep, find, lstooltool: <path> or tool: <pattern>
(other)tooltool name

Error detection

  • stopReason in message_end: "error" or "aborted" -> ok=False
  • No agent_end received -> CompletedEvent(ok=False, error="stream ended...")

Config keys

[pi]
provider = "anthropic"   # or "openai", "google", etc.
model = "claude-sonnet-4-5-20250929"
extra_args = []

Adding a new engine

To add a new engine runner:

  1. Create src/untether/runners/myengine.py
  2. Define schemas in src/untether/schemas/myengine.py
  3. Implement MyEngineRunner(JsonlSubprocessRunner) with required template methods
  4. Export BACKEND = EngineBackend(id="myengine", build_runner=..., cli_cmd="myengine")
  5. Register in pyproject.toml entry points:
    myengine = "untether.runners.myengine:BACKEND"
  6. Add reference docs in docs/reference/runners/myengine/
  7. Add tests mirroring existing tests/test_*_runner.py patterns

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.