Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,5 @@ If you want a clean re-port (e.g. when upstream releases v0.2.0), the rebuild re
- Upstream deslop: [cursor/plugins/cursor-team-kit/skills/deslop @ e46364b](https://github.com/cursor/plugins/tree/e46364b8be46000b7df0f260550cd712afbb8d36/cursor-team-kit/skills/deslop) — MIT, (c) 2026 Cursor.
- babysit: independently authored; workflow informed by Cursor's public `/babysit` behavior — no code or prose copied.
- Inspected for prior-art decisions: [v1truv1us/ai-eng-system](https://github.com/v1truv1us/ai-eng-system) (namespaces pstack under `pstack/` but keeps Cursor refs intact); [Evan-Kim2028/agent-fleet](https://github.com/Evan-Kim2028/agent-fleet) (vendors pstack under `base-kit/pstack/`, same posture).

- Add opt-in Cursor external workers through `cursor-agent`, exact model slugs and `@default`, private per-run permissions, JSON receipts, and setup probes. API-key authentication is checked by model execution; private configuration preserves the global HTTP/1 transport option without copying other user settings. Parent completion accepts explicitly pinned Cursor arguments without claiming served-model verification. Cursor project hooks/rules and recursive subagents remain provider limitations.
2 changes: 2 additions & 0 deletions NOTICE.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ Files authored for this port (not derived from upstream):
- `README.md`
- `CHANGES.md`
- `LICENSE-cursor-team-kit` (copied verbatim from upstream cursor-team-kit MIT)

- Cursor external-provider support in `skills/poteto-mode/scripts/runner/` and its setup/dispatch documentation is independently authored for this port against Cursor CLI public interfaces; no Cursor CLI implementation code is copied.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ Both apps read the same pstack skills. Only the way they start those skills and
| Other models | Codex and Grok run through their signed-in command-line tools. | Claude and Grok run through their signed-in command-line tools. |
| Skills and workflows | Shared with Codex. | Shared with Claude Code. |

Cursor models can also join as optional external workers through the signed-in `cursor-agent` CLI. Choose an exact slug from `cursor-agent models` and configure `cursor:<slug>@default` with `/setup-pstack`; the CLI has no separate effort flag. This uses Cursor's account access and limits, and does not make Cursor a parent harness for this port.

Grok can take part in a multi-model review. You cannot use Grok as the main app running pstack.

## Learn from the original
Expand Down
8 changes: 7 additions & 1 deletion docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This page contains the full skill, dependency, runtime, and porting reference. For the plain-English introduction and quick start, see the [main README](../README.md).

[Poteto](https://x.com/poteto)'s [pstack](https://github.com/cursor/plugins/tree/main/pstack), adapted to run in Claude Code and Codex without Cursor. One shared skill tree serves both harnesses; Grok remains available as a model-provider lane. Version 1.4.1 is synced to Cursor pstack v0.15.1 at `f8abeddd1862dc73704e3d719dd73df0d51b8c71`. See [UPSTREAM.md](../UPSTREAM.md) for the exact sync contract.
[Poteto](https://x.com/poteto)'s [pstack](https://github.com/cursor/plugins/tree/main/pstack), adapted to run in Claude Code and Codex without Cursor. One shared skill tree serves both harnesses; Grok and Cursor remain available as external model-provider lanes. Version 1.4.1 is synced to Cursor pstack v0.15.1 at `f8abeddd1862dc73704e3d719dd73df0d51b8c71`. See [UPSTREAM.md](../UPSTREAM.md) for the exact sync contract.

Original by Lauren Tan. This distribution builds on Michael Denyer's [pstack-claude](https://github.com/michael-denyer/pstack-claude) port and retains its history and MIT attribution. It imports seven MIT-licensed skills from [cursor-team-kit](https://github.com/cursor/plugins/tree/main/cursor-team-kit): `deslop`, `thermo-nuclear-code-quality-review`, `make-pr-easy-to-review`, `fix-ci`, `fix-merge-conflicts`, `get-pr-comments`, `what-did-i-get-done`.

Expand Down Expand Up @@ -227,3 +227,9 @@ MIT. Three upstream LICENSE files are preserved:
- [LICENSE](../LICENSE) — pstack (Lauren Tan)
- [LICENSE-cursor-team-kit](../LICENSE-cursor-team-kit) — Cursor (covers the `deslop` and `thermo-nuclear-code-quality-review` skills)
- [LICENSE-superpowers](../LICENSE-superpowers) — superpowers, Jesse Vincent (covers the vendored `hooks/run-hook.cmd`)

### Optional Cursor workers

`cursor:<exact-model-slug>@default` starts `cursor-agent` from either parent; discover slugs with `cursor-agent models`. Setup probes only assigned optional Cursor models. The adapter preserves the exact selection, successful JSON result, session ID, and optional token usage. A missing model report is recorded as pinned-argv evidence, not verified served-model identity.

Cursor workers use their own tools and do not inherit the parent's MCP tools or conversation. Supply complete task context. Read-only uses ask mode with shell/write denied; writers need a dedicated workspace and sandbox. A private per-run configuration isolates global permission settings without copying credentials. Project configuration and startup hooks can still load, and recursive Cursor subagents cannot currently be disabled by a supported flag. See [provider dispatch](../plugins/pstack/skills/poteto-mode/references/provider-dispatch.md) for the full contract.
2 changes: 1 addition & 1 deletion plugins/pstack/skills/arena/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Start all N lanes in one fan-out phase through the provider-dispatch contract. N

Each rationale names the alternatives the candidate considered and what it rejected.

An external lane counts only when its receipt says `complete` and carries either a matching `provider-report` or Codex's exact `pinned-argv` evidence; a native lane counts when its tool transcript returns the assigned model's result. If a candidate fails, proceed with N-1 and note the exact dropout in the synthesis record. Never replace it with another provider silently.
An external lane counts only when it satisfies the shared provider-dispatch completion contract, including matching `provider-report` or permitted exact `pinned-argv` evidence; a native lane counts when its tool transcript returns the assigned model's result. If a candidate fails, proceed with N-1 and note the exact dropout in the synthesis record. Never replace it with another provider silently.

## Phase C: Cross-judge

Expand Down
2 changes: 2 additions & 0 deletions plugins/pstack/skills/poteto-mode/references/codex-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@ Some triggers name skills that ship with Claude Code, not pstack. They do not ex
## Instructions file

Where a pstack skill says "your instructions file", on Codex that is `AGENTS.md` (project root, plus `~/.codex/AGENTS.md` global). On Claude Code it is `CLAUDE.md`.

Cursor descriptors (`cursor:<exact-model-slug>@default`) always use the external runner through `cursor-agent`. Follow the optional Cursor contract in `provider-dispatch.md`; keep MCP-dependent roles native.
28 changes: 19 additions & 9 deletions plugins/pstack/skills/poteto-mode/references/provider-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ pstack model choices are provider-qualified descriptors:
| grok | grok-4.6-fast-xhigh | grok | grok-4.6 | xhigh | low medium high xhigh max | - |
| opus | opus | claude | opus | xhigh | low medium high xhigh max | opus |

The allowed effort universe is exactly `low`, `medium`, `high`, `xhigh`, `max`. First-run requested efforts are the Default effort cell of each row. A Claude-native agent stem of `-` means the family has no Claude-native agent. Otherwise the shipped agent name is `pstack-<stem>-<effort>`.
For the baseline matrix, the allowed effort universe is exactly `low`, `medium`, `high`, `xhigh`, `max`. First-run requested efforts are the Default effort cell of each row. A Claude-native agent stem of `-` means the family has no Claude-native agent. Otherwise the shipped agent name is `pstack-<stem>-<effort>`.

`fable` and `opus` are Claude Code's rolling aliases. Claude resolves each alias to the latest available family revision. A runner receipt keeps the requested alias in `model` and the concrete provider-reported revision in `reportedModel`; verification accepts only a numeric `claude-fable-*` or `claude-opus-*` revision from the matching family.

## Optional Cursor models

Cursor is an external provider from both parents. Install and authenticate `cursor-agent`, run `cursor-agent models`, and choose an exact available slug as `cursor:<slug>@default` (for example, `cursor:composer-2.5@default` when listed). `default` means no separate effort flag is available; select any reasoning variant by its exact model slug. Do not translate Claude/Codex/Grok slugs into Cursor slugs or use Cursor's `auto` selector. Model availability and subscription limits remain Cursor's responsibility. Adding a Cursor lane does not alter the four baseline families.

## Read-time normalization

Normalize configured descriptors before matching them to the matrix or choosing a route. If a provider-qualified Claude model starts with `claude-fable-` or `claude-opus-` and its remaining revision contains only digits and hyphens, replace that model component in memory with `fable` or `opus`. Preserve provider, effort, role, and lane order. Use only the normalized descriptor for native dispatch or runner argv. Never pass the versioned predecessor to Claude.
Expand All @@ -31,10 +35,10 @@ This read-time rule makes an older installed sheet use the latest family revisio

The top-level harness resolves the route once. A child receives an assigned provider, model, effort, access mode, prompt, working directory, and output path. A child never detects the harness, chooses a provider, or launches another model. Environment markers may corroborate the top-level harness before fan-out, but nested processes inherit parent markers and must not use them for routing.

| Parent | `claude:*` | `codex:*` | `grok:*` |
|---|---|---|---|
| Claude Code | native `Agent` | external runner | external runner |
| Codex | external runner | native `spawn_agent` | external runner |
| Parent | `claude:*` | `codex:*` | `grok:*` | `cursor:*` |
|---|---|---|---|---|
| Claude Code | native `Agent` | external runner | external runner | external runner |
| Codex | external runner | native `spawn_agent` | external runner | external runner |

`inherit-parent` and `auto` remain aliases. They use the parent's current model and effort through its native subagent primitive. In a panel they still consume one lane, but they reduce provider diversity; say so in the synthesis record.

Expand All @@ -54,9 +58,9 @@ The launcher lives at `skills/poteto-mode/scripts/runner/pstack-runner` under th
```text
pstack-runner \
--parent <claude|codex> \
--provider <claude|codex|grok> \
--provider <claude|codex|grok|cursor> \
--model <real CLI model> \
--effort <low|medium|high|xhigh|max> \
--effort <low|medium|high|xhigh|max|default> \
--mode <read-only|isolated-write> \
--prompt <unique prompt file> \
--cwd <repository or dedicated worktree> \
Expand All @@ -82,6 +86,12 @@ The runner and its preflight have no implicit timeout. Do not invent a duration

Read-only mode maps to Claude plan mode with project-only settings and an explicit tool list, Codex's read-only sandbox, and Grok plan mode plus its `read-only` sandbox and read-oriented tool list. Grok's built-in read-only profile deliberately keeps its own state and system temporary directories writable, so point a read-only Grok lane at the actual checkout rather than a worktree under `/tmp`, `/var/tmp`, or the host's temporary directory. `isolated-write` maps to Claude `acceptEdits` with project-only settings, Codex `workspace-write`, and Grok `acceptEdits` plus its `workspace` sandbox and write-capable tool list. Give every writer only a dedicated worktree or output directory. Never route a writer into the primary checkout.

Cursor uses the explicit `cursor-agent` executable, not `agent` (which can name another CLI). For stored OAuth credentials, preflight requires `cursor-agent status --format json` to return `isAuthenticated: true`. With a nonblank `CURSOR_API_KEY`, preflight checks `cursor-agent --version` only and records that authentication is deferred to the real model invocation; Cursor status does not report API-key authentication. The key stays in the environment. Authentication failures from execution fail the lane. Setup always performs a real model probe because preflight alone does not prove model access. The runner sends the prompt through stdin, pins `--model`, requests a successful JSON terminal result, and uses `--workspace` with `--sandbox enabled`. Read-only adds `--mode ask` and denies `Write(**)` and `Shell(*)`; it cannot run shell-based tests. Writers use the sandbox in their dedicated workspace without `--force` or `--yolo`.

Each Cursor attempt exclusively creates a private `<receipt>.cursor-config` directory (0700) containing `cli-config.json` (0600), sets `CURSOR_CONFIG_DIR` for preflight and execution, and removes that directory on completion, failure, timeout, or handled cancellation. Existing paths are never overwritten or removed. The temporary permissions deny `Mcp(*:*)` and `WebFetch(*)`; no user configuration is edited and no credentials are copied. Cursor's authentication storage is separate from this configuration directory. Before overriding the directory, the runner reads the original global configuration (`CURSOR_CONFIG_DIR`, otherwise `XDG_CONFIG_HOME/cursor`, otherwise `~/.cursor`) and preserves only a boolean `network.useHttp1ForAgent`, needed by some proxies. It never copies permissions, hooks, credentials, endpoints, or other settings. An unreadable or malformed existing configuration fails the lane instead of silently losing its transport setting.

Cursor's CLI currently exposes no supported switch to disable recursive subagents, project rules, skills, plugins, or hooks. The runner passes `--trust` for the assigned workspace so headless execution can start; this may authorize project startup hooks. Use only trusted workspaces and keep the parent-owned assignment in the prompt. These settings are not a clean-room guarantee, and the receipt does not prove that project hooks or every descendant were sandboxed. Cursor's native child tools also differ from the parent's tools; MCP-dependent work must stay native. See Cursor's [configuration](https://cursor.com/docs/cli/reference/configuration), [permissions](https://cursor.com/docs/cli/reference/permissions), and [output format](https://cursor.com/docs/cli/reference/output-format) contracts.

Every concurrent external lane needs distinct prompt, output, and receipt paths. The launcher reserves output and receipt paths exclusively and refuses to overwrite them.

## Completion and dropouts
Expand All @@ -90,10 +100,10 @@ Success requires all of these:

1. Exit status `0`.
2. Receipt status `complete`.
3. Either `modelVerified: true` with `modelEvidence: "provider-report"`, or a Codex receipt with `reportedModel: null`, `modelVerified: false`, and `modelEvidence: "pinned-argv"`. For Claude's `fable` and `opus` aliases, the concrete provider report must belong to the requested family. Codex 0.149.0 accepts the exact `--model` argument but does not report the served model in its JSONL stream.
3. Either `modelVerified: true` with `modelEvidence: "provider-report"`, or a Codex or Cursor receipt with `reportedModel: null`, `modelVerified: false`, and `modelEvidence: "pinned-argv"`. For Claude's `fable` and `opus` aliases, the concrete provider report must belong to the requested family. Codex 0.149.0 and Cursor accept an exact `--model` argument but may omit served-model identity in their result. For pinned-argv evidence, verify the receipt provider, model, effort, and exact `--model` value against the assigned descriptor; Cursor also requires `effort: "default"`. Do not describe pinned arguments as provider-verified identity.
4. A non-empty output file.

The receipt also carries elapsed time, token usage when the CLI exposes it, and cost when available. Keep it with the arena or review artifacts so parent-harness comparisons are evidence-based.
The receipt also carries elapsed time, token usage when the CLI exposes it, and cost when available. Cursor returns session IDs and may return token usage; missing model identity, usage, or cost stays null. Keep it with the arena or review artifacts so parent-harness comparisons are evidence-based.

Any missing CLI, failed login, unavailable model, explicit timeout, cancellation, catchable post-reservation launcher failure, non-zero child exit, malformed result, or model mismatch is a receipt-bearing dropout. Record it and apply the calling skill's existing dropout policy. A `cancelled` receipt proves that the runner received the signal; its `signal` field is non-null only when the runner sent that signal to a still-active direct CLI child, and remains null when cancellation only stopped a post-exit pipe drain. The provider CLI owns any processes it starts beneath that direct child; the receipt does not claim a process-tree kill. Do not delete or overwrite the receipt. Never substitute the parent model, retry another provider, or reinterpret an external descriptor as a native model slug.

Expand Down
4 changes: 2 additions & 2 deletions plugins/pstack/skills/poteto-mode/scripts/runner/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
UsageError,
} from "./types.ts";

const HELP = `Usage: pstack-runner --parent <claude|codex> --provider <claude|codex|grok> \\
const HELP = `Usage: pstack-runner --parent <claude|codex> --provider <claude|codex|grok|cursor> \\
--model <slug> --effort <level> --mode <read-only|isolated-write> \\
--prompt <file> --cwd <dir> --output <file> --receipt <file> [--timeout <seconds>]

Expand Down Expand Up @@ -98,7 +98,7 @@ export function parseArgs(argv: readonly string[]): RunnerOptions | null {
parent: oneOf("parent", stringValue(parsed.values.parent), PARENTS) as Parent,
provider: oneOf("provider", stringValue(parsed.values.provider), PROVIDERS) as Provider,
model: required("model", stringValue(parsed.values.model)),
effort: oneOf("effort", stringValue(parsed.values.effort), EFFORTS) as Effort,
effort: oneOf("effort", stringValue(parsed.values.effort), [...EFFORTS, "default"]) as Effort,
mode,
promptPath: required("prompt", stringValue(parsed.values.prompt)),
cwd: required("cwd", stringValue(parsed.values.cwd)),
Expand Down
19 changes: 19 additions & 0 deletions plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cursorHasApiKey } from "./cursor.ts";
import type {
AccessMode,
Effort,
Expand All @@ -13,6 +14,12 @@ export interface CommandSpec {

export function preflightCommand(provider: Provider): CommandSpec {
switch (provider) {
case "cursor":
return {
command: "cursor-agent",
args: cursorHasApiKey() ? ["--version"] : ["status", "--format", "json"],
stdin: "none",
};
case "claude":
return {
command: "claude",
Expand Down Expand Up @@ -65,6 +72,18 @@ function effortOverride(effort: Effort): string {

export function invocationCommand(options: RunnerOptions): CommandSpec {
switch (options.provider) {
case "cursor":
return {
command: "cursor-agent",
args: [
"--print", "--output-format", "json", "--trust",
"--model", options.model,
"--workspace", options.cwd,
"--sandbox", "enabled",
...(options.mode === "read-only" ? ["--mode", "ask"] : []),
],
stdin: "prompt",
};
case "claude":
return {
command: "claude",
Expand Down
Loading