Skip to content
Merged
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
9 changes: 9 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# CHANGES — applied substitutions

## Unreleased: OpenRouter gateway, any model

- New gateway provider `openrouter` ([#7](https://github.com/thisguymartin/pstack-flex/issues/7)). One `OPENROUTER_API_KEY` reaches any model in OpenRouter's catalog through the stock `claude` binary, the same path DeepSeek and MiniMax use. There is no allowlist: the flex matrix carries one open `openrouter` row, each distinct model ID is its own family, and setup's live probe on the named model is the gate. The OpenRouter probe reads its marker from a file, so it proves a tool call.
- The runner refuses an OpenRouter ID without a namespace and OpenRouter's own `openrouter/*` routers, which pick the model server-side. OpenRouter reports must match the requested ID exactly apart from case; the other gateways keep their prefix rule. Only OpenRouter lanes get the empty `ANTHROPIC_API_KEY` its guide requires, so DeepSeek and MiniMax environments are unchanged.
- Panel diversity counts the lab behind the model. An OpenRouter lane counts as its model ID's namespace, and `anthropic`, `openai`, `x-ai`, `deepseek`, and `minimax` match the direct providers.
- Runner fixes found by the OpenRouter dry run, which apply to every lane: Claude Code 2.1.289's 401 result ("Failed to authenticate", `"api_error_status":401`) now classifies as `unauthenticated` instead of `child-failed`, and `usage.output_tokens_details.thinking_tokens` is recorded as `reasoningTokens`.
- `scripts/probe-openrouter.sh` runs the #7 route battery (V6 in `docs/LANES.md`). It spends real credit, so it is not part of `check.sh` or CI.
- Design and flow diagrams: [docs/plans/2026-10-05-openrouter-gateway.md](docs/plans/2026-10-05-openrouter-gateway.md).

## Unreleased: project model sheets and GPT-6.1 Sol

- `setup-pstack` asks on every run whether to configure the global sheet or a project sheet. A project sheet lives at `<project root>/.claude/pstack-models.md` (Claude Code) or `<project root>/.codex/pstack-models.md` (Codex), starts from the global assignments, and is listed in `.git/info/exclude` so it never reaches a commit. It needs no CLAUDE.md include and no AGENTS.md block.
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ Every pstack role (who writes code, who explores, who sits on a review panel) ma
| `deepseek-pro` | `deepseek:deepseek-v4-pro@high` | `DEEPSEEK_API_KEY` | none; selectable |
| `minimax` | `minimax:MiniMax-M3@high` | `MINIMAX_API_KEY` | none; selectable |
| `minimax-preview` | `minimax:MiniMax-M3.1-Flash-Preview@high` | `MINIMAX_API_KEY` (Token Plan) | none; selectable |
| `openrouter` | `openrouter:<namespace>/<model>@high`, any OpenRouter model | `OPENROUTER_API_KEY` | none; selectable |

The default review panel is `claude:fable@max, codex:gpt-6-astra@high, grok:grok-4.7@xhigh, claude:opus@max`: four lanes across three providers. Architect sketches default to `codex:gpt-6-astra@high, claude:fable@max`. Any family can take any role. Panels must span at least two providers, and two models from one provider count as one, because the adversarial signal comes from model diversity.
The default review panel is `claude:fable@max, codex:gpt-6-astra@high, grok:grok-4.7@xhigh, claude:opus@max`: four lanes across three providers. Architect sketches default to `codex:gpt-6-astra@high, claude:fable@max`. Any family can take any role. Panels must span at least two providers, and two models from one provider count as one, because the adversarial signal comes from model diversity. An OpenRouter lane counts as the lab that made its model.

The DeepSeek and MiniMax lanes run the stock `claude` binary against the lab's Anthropic-compatible endpoint with that lab's key, in an isolated config directory, with inherited Anthropic routing stripped. A lane refuses to start if it finds a claude.ai login in that directory, so a subscription credential can never reach a third-party endpoint. Their receipts keep real token usage but set `costUsd` to null (Claude Code prices at Anthropic rates); the price table is in [docs/LANES.md](docs/LANES.md). Anthropic does not support pointing Claude Code at non-Anthropic endpoints; use synthetic data for gateway testing and keep keys in your local environment.
The DeepSeek, MiniMax, and OpenRouter lanes run the stock `claude` binary against an Anthropic-compatible endpoint with that provider's key, in an isolated config directory, with inherited Anthropic routing stripped. A lane refuses to start if it finds a claude.ai login in that directory, so a subscription credential can never reach a third-party endpoint. Their receipts keep real token usage but set `costUsd` to null (Claude Code prices at Anthropic rates); the price table is in [docs/LANES.md](docs/LANES.md). Anthropic does not support pointing Claude Code at non-Anthropic endpoints; use synthetic data for gateway testing and keep keys in your local environment. Through OpenRouter, a role can use any model in its catalog, such as `openrouter:moonshotai/kimi-k3@high`; setup's live probe on that model is the only gate.

### How a role becomes a lane

Expand All @@ -62,7 +63,7 @@ flowchart LR
P -->|"any other provider"| R["pstack-runner<br/>one process per lane"]
R --> C1["codex CLI"]
R --> C2["grok CLI"]
R --> C3["claude CLI + env<br/>DeepSeek or MiniMax endpoint"]
R --> C3["claude CLI + env<br/>DeepSeek, MiniMax, or OpenRouter endpoint"]
N --> O["Output + receipt<br/>model, effort, tokens, status"]
C1 --> O
C2 --> O
Expand All @@ -73,7 +74,7 @@ The parent resolves every route once, before fan-out. Children never detect the

## Install

You need a current Claude Code or Codex installation and [Bun](https://bun.sh) for the lane runner. Sign in only to the CLIs whose plans you have (Claude Code, Codex, Grok), and export `DEEPSEEK_API_KEY` or `MINIMAX_API_KEY` in the shell that starts your session for the gateway lanes. Any subset works, down to a zero-subscription setup on two keys.
You need a current Claude Code or Codex installation and [Bun](https://bun.sh) for the lane runner. Sign in only to the CLIs whose plans you have (Claude Code, Codex, Grok), and export `DEEPSEEK_API_KEY`, `MINIMAX_API_KEY`, or `OPENROUTER_API_KEY` in the shell that starts your session for the gateway lanes. Any subset works, down to a zero-subscription setup on two keys.

### Claude Code

Expand Down Expand Up @@ -180,10 +181,10 @@ Both apps read the same pstack skills. Only the way they start those skills and
| Start poteto-mode | Run `/pstack:poteto-mode` or ask for pstack by name. A small startup instruction keeps Claude from starting pstack skills on its own. | Ask for `pstack:poteto-mode` by name. Codex runs the same startup instruction, so pstack skills also wait for a request there. |
| Runs inside the app | Claude models stay inside Claude Code. | The Codex families stay inside Codex. |
| Other models | The Codex families and Grok run through their signed-in command-line tools. | Claude and Grok run through their signed-in command-line tools. |
| Gateway models | DeepSeek and MiniMax always run through the external runner with an isolated config directory, never as a native agent. | Same. |
| Gateway models | DeepSeek, MiniMax, and OpenRouter always run through the external runner with an isolated config directory, never as a native agent. | Same. |
| Skills and workflows | Shared with Codex. | Shared with Claude Code. |

Grok, DeepSeek, and MiniMax can take part in a multi-model review. You cannot use any of them as the main app running pstack.
Grok, DeepSeek, MiniMax, and OpenRouter models can take part in a multi-model review. You cannot use any of them as the main app running pstack.

## Upstream

Expand Down
1 change: 1 addition & 0 deletions UPSTREAM-FLEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ All flex changes are additive and live in port-owned files so upstream merges st
- `docs/LANES.md`, this file, the README fork section, and the NOTICE/LICENSE/CHANGES additions
- `plugins/pstack/hooks/session-start-context.md`, which the fork rewrote from open-pstack's auto-fire mandate into an opt-in gate, and the docs lines that describe it
- The `intake` and `diff-behavior` skills: `plugins/pstack/skills/intake/` and `plugins/pstack/skills/diff-behavior/`. Upstream has no equivalent, so they never conflict.
- The OpenRouter gateway: its `GATEWAY_SPECS` row and `openRouterModelRefusal` in `runner/flex-providers.ts`, the router check in `run.ts` `validateOptions`, the exact-match branch in `parse-output.ts` `reportedModelMatches`, the open `openrouter` row and its paragraph in the flex section, the lab rule in the panel-diversity paragraph, and the OpenRouter lines in `skills/setup-pstack/SKILL.md`.
- The lane journal: `runner/flex-journal.ts` and its test (new), its call sites in `runner/{types,run,cli}.ts` (an optional stdout callback on the model run, the journal opened after output reservation and finished on both return paths, and the `--label` flag), the journal tests in `run.test.ts`, and the pstack-flex paragraph after the invocation block in `references/provider-dispatch.md`. On a sync, keep these call sites; they change no receipt or exit status.

Every upstream skill body is byte-unchanged except for the default-descriptor mentions listed above. Since [#17](https://github.com/thisguymartin/pstack-flex/issues/17), the stock matrix carries three fork-owned GPT-6 rows and the first-run sheet uses them, so those two surfaces conflict on every upstream sync and are resolved by hand: keep the fork's rows and defaults, take upstream's wording for everything else.
Expand Down
Loading
Loading