home / skills / tkersey / dotfiles / casp

casp skill

/codex/skills/casp

This skill enables automated control of the codex app-server via a v2 proxy, streaming JSONL, auto approvals, and multi-instance orchestration.

npx playbooks add skill tkersey/dotfiles --skill casp

Review the files below or copy the command above to add this skill to your agents.

Files (8)
SKILL.md
7.6 KB
---
name: casp
description: Run a v2-only Node JSONL proxy that spawns `codex app-server` and exposes an automation-friendly stream API. Use when you need to drive the app-server programmatically (automation/orchestration/session mining), have subagents send updates or code patches into live sessions, auto-handle approvals, forward server requests with deterministic timeouts, mine sessions via `thread/*`, steer active turns (`turn/steer`), or run N parallel instances (each instance is one proxy + one app-server child).
---

# casp (App-Server Protocol)

## Overview

Casp ships a small Node proxy (`scripts/casp_proxy.mjs`) that:

- Spawns `codex app-server`.
- Performs the required handshake (`initialize` -> `initialized`) with `experimentalApi: true` and optional `optOutNotificationMethods`.
- Reads/writes JSONL over stdio.
- Auto-accepts v2 approval requests.
- Forwards v2 server requests to the orchestrator.
- Rejects deprecated legacy approval requests (`execCommandApproval`, `applyPatchApproval`).
- Fails forwarded requests deterministically on timeout (default `30000` ms).
- Emits a lossless, automation-friendly event stream (includes the raw app-server message plus derived routing keys).

This skill assumes `codex` is available on PATH and does not require access to any repo source tree.

## Terminology (Instances)

- An "instance" is one `casp_proxy` process plus its spawned app-server child process.
- Each instance has its own JSONL stream and its own `sessionId`.
- "N instances" means N parallel proxy+app-server pairs; it is not N threads/turns inside one instance.
- Isolation tip: for multi-instance runs, prefer per-instance `--state-file` (or the runner's `--state-file-dir`) if you don't want instances to share state.

## Trigger cues

- "instances" / "multi-instance" / "parallel sessions"
- app-server protocol control (JSONL proxy, JSON-RPC methods)
- session mining (thread/turn inventory, export/index)
- steering/resume (`turn/steer`, `thread/resume`)

## Workflow

1. Start the proxy.
   - Run `node scripts/casp_proxy.mjs` from the casp skill directory (example: `node ~/.dotfiles/codex/skills/casp/scripts/casp_proxy.mjs`).
   - Optional: pass `--cwd /path/to/workspace` to control where the app-server runs. By default, state is written under `~/.codex/casp/state/<workspace-hash>.json`.
   - Optional: pass `--state-file PATH` to override the default state location.
   - Optional: tune forwarded request fail-fast behavior with `--server-request-timeout-ms <N>` (0 disables timeout).
   - Optional: pass one or more `--opt-out-notification-method METHOD` flags to suppress known noisy notifications for the connection.
   - Wait for a `casp/ready` event.

   For N instances in parallel, prefer the instance runner:
   - `node scripts/casp_instance_runner.mjs --cwd /path/to/workspace --instances N`

2. Drive the app-server by sending requests to the proxy.
   - Send `casp/request` messages (method + params) to proxy stdin.
   - Proxy assigns request ids (unless you supply one), forwards to app-server, and emits `casp/fromServer` responses.
   - Optional smoke check: run `node scripts/casp_smoke_check.mjs --cwd /path/to/workspace`.

3. Stream and route notifications.
   - Consume `casp/fromServer` events and route by `threadId` / `turnId` / `itemId`.
   - Treat the proxy stream as the source of truth; the raw wire message is always included under `msg`.

4. Handle forwarded server requests.
   - Only reply when casp emits `casp/serverRequest` (these are the server requests casp did not auto-handle).
   - Respond with `casp/respond` using the same `id`.
   - If your response is malformed for a typed v2 request, casp sends a deterministic JSON-RPC error upstream instead of hanging.
   - If you do not reply in time, casp emits `casp/serverRequestTimeout` and fails that request upstream.
   - Approvals are auto-accepted (including best-effort execpolicy amendments) and will not block you.

5. Mine sessions (optional).
   - Use `thread/list` and `thread/read` (optionally `includeTurns:true`) to build your own index.
   - The server is not a search engine; extract data and index externally.

## Dedicated API Helpers

Use `scripts/casp_client.mjs` convenience wrappers when you want typed intent rather than raw method strings:

- `resumeThread(params)` -> `thread/resume`
- `steerTurn(params)` -> `turn/steer`
- `listExperimentalFeatures(params)` -> `experimentalFeature/list`

## Dynamic Tools (Optional)

If you opt into dynamic tools, register them on `thread/start` via `dynamicTools` (experimental API surface).
When the server emits `casp/serverRequest`:
- For `method: "item/tool/call"`, run the tool in your orchestrator and reply with `casp/respond`.
- For `method: "item/tool/requestUserInput"` (experimental), collect answers and return `{ answers: ... }`.
- For `method: "account/chatgptAuthTokens/refresh"`, return refreshed tokens or a deterministic error.

## Proxy Protocol (stdin/stdout)

The proxy itself speaks JSONL over stdio.

### stdin -> casp

- `casp/request` sends a JSON-RPC request to `codex app-server`:

```json
{
  "type": "casp/request",
  "clientRequestId": "any-string",
  "method": "thread/start",
  "params": { "cwd": "/path", "experimentalRawEvents": false }
}
```

- `casp/respond` answers a server-initiated request forwarded by casp:

```json
{
  "type": "casp/respond",
  "id": 123,
  "result": {
    "contentItems": [{ "type": "inputText", "text": "..." }],
    "success": true
  }
}
```

- `casp/send` forwards a raw JSON-RPC message to `codex app-server` (advanced escape hatch):

```json
{
  "type": "casp/send",
  "msg": { "method": "thread/list", "id": "raw-1", "params": { "cursor": null } }
}
```

- `casp/state/get` emits the current proxy state.
- `casp/stats/get` emits a stats snapshot (uptime, queue depth, counts).
- `casp/exit` shuts down the proxy.

### stdout <- casp

- `casp/ready` indicates the proxy finished handshake.
- `casp/fromServer` is emitted for every JSON message from `codex app-server`.
- `casp/toServer` is emitted for every JSON message sent to `codex app-server` (includes auto-approvals and handshake).
- `casp/serverRequest` is emitted for server-initiated requests that require an orchestrator response (tool calls, auth refresh, etc.).
- `casp/serverRequestTimeout` is emitted when a forwarded server request is failed due to timeout.
- `casp/stats` and `casp/ioPaused`/`casp/ioResumed` help you monitor backpressure.

All events include:

- `seq` (monotonic)
- `ts` (ms since epoch)
- `sessionId` (unique per proxy instance)
- derived keys `threadId` / `turnId` / `itemId` when present
- `msg` (the raw app-server message; lossless)

## Canonical Schema Source

Use your installed `codex` binary to generate schemas that match your version:

```sh
codex app-server generate-ts --out DIR
codex app-server generate-json-schema --out DIR

# If you need experimental methods/fields (e.g. dynamic tools), include:
codex app-server generate-ts --experimental --out DIR
codex app-server generate-json-schema --experimental --out DIR
```

## Local References

Read `references/codex_app_server_protocol.md` for a protocol map and the recommended routing/response strategy.

## Resources

### references/

Protocol notes for fast lookup during implementation.

### scripts/

Runnable Node proxy for orchestration.

Included:
- `scripts/casp_proxy.mjs` (the proxy)
- `scripts/casp_client.mjs` (JS wrapper: spawn proxy + request() + event stream)
- `scripts/casp_example_orchestrator.mjs` (example orchestration script)
- `scripts/casp_instance_runner.mjs` (run one method across many parallel casp sessions/instances)
- `scripts/casp_smoke_check.mjs` (smoke-checks `experimentalFeature/list`, `thread/resume`, `turn/steer`)

Overview

This skill runs a v2-only Node JSONL proxy that spawns codex app-server and exposes an automation-friendly stream API. It provides an evented, lossless JSONL stream, deterministic timeout handling for forwarded server requests, and auto-accepts v2 approvals so orchestrators can drive sessions programmatically. Each instance is a proxy + one app-server child, enabling parallel isolated sessions.

How this skill works

The proxy performs the app-server handshake (initialize -> initialized) with experimentalApi enabled, reads/writes JSONL over stdio, and forwards messages between the app-server and your orchestrator. It auto-handles v2 approval flows, rejects legacy approval types, and emits typed events (casp/ready, casp/fromServer, casp/serverRequest, etc.) that include raw wire messages plus derived routing keys. Forwarded server requests must be answered by the orchestrator or will fail after a configurable timeout.

When to use it

  • Programmatic automation or orchestration of codex app-server sessions
  • Running multiple parallel, isolated app-server instances (N proxy+server pairs)
  • Mining or indexing sessions, threads, and turns with deterministic event routing
  • Steering or resuming active turns (turn/steer, thread/resume) from external agents
  • Integrating dynamic tools or auto-handling server-initiated tool calls and auth refresh

Best practices

  • Start each parallel instance with its own --state-file or state-file-dir to avoid shared state
  • Consume casp/fromServer as the source of truth; use included msg for lossless replay
  • Reply only to casp/serverRequest events and use matching id in casp/respond to avoid timeouts
  • Use the client helpers (casp_client.mjs) for typed intent rather than raw method strings
  • Tune --server-request-timeout-ms for deterministic failure semantics; 0 disables timeout

Example use cases

  • Run N isolated app-server sessions to parallelize workload or load-test orchestration logic
  • Have subagents send code patches or updates into live sessions via casp/request
  • Automate approvals and execpolicy amendments while forwarding tool calls to orchestrator tools
  • Mine thread and turn inventories (thread/list, thread/read) for offline indexing
  • Steer active conversations programmatically using turn/steer and thread/resume

FAQ

Does this require modifying the workspace repository?

No. The proxy spawns codex app-server and manages state under ~/.codex/casp by default; passing --cwd controls where the app-server runs.

How are server-initiated tool calls handled?

casp emits casp/serverRequest for tool calls. Run the tool in your orchestrator and reply with casp/respond; casp will fail the request after the configured timeout if you don't reply.